delete.c revision 299742
1156230Smux/* 2156230Smux * delete.c: wrappers around wc delete functionality. 3156230Smux * 4156230Smux * ==================================================================== 5156230Smux * Licensed to the Apache Software Foundation (ASF) under one 6156230Smux * or more contributor license agreements. See the NOTICE file 7156230Smux * distributed with this work for additional information 8156230Smux * regarding copyright ownership. The ASF licenses this file 9156230Smux * to you under the Apache License, Version 2.0 (the 10156230Smux * "License"); you may not use this file except in compliance 11156230Smux * with the License. You may obtain a copy of the License at 12156230Smux * 13156230Smux * http://www.apache.org/licenses/LICENSE-2.0 14156230Smux * 15156230Smux * Unless required by applicable law or agreed to in writing, 16156230Smux * software distributed under the License is distributed on an 17156230Smux * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18156230Smux * KIND, either express or implied. See the License for the 19156230Smux * specific language governing permissions and limitations 20156230Smux * under the License. 21156230Smux * ==================================================================== 22156230Smux */ 23156230Smux 24156230Smux/* ==================================================================== */ 25156230Smux 26156230Smux 27156230Smux 28156230Smux/*** Includes. ***/ 29156230Smux 30156230Smux#include <apr_file_io.h> 31156230Smux#include "svn_hash.h" 32156230Smux#include "svn_types.h" 33156230Smux#include "svn_pools.h" 34156230Smux#include "svn_wc.h" 35156230Smux#include "svn_client.h" 36156230Smux#include "svn_error.h" 37156230Smux#include "svn_dirent_uri.h" 38156230Smux#include "svn_path.h" 39156230Smux#include "client.h" 40156230Smux 41156230Smux#include "private/svn_client_private.h" 42156230Smux#include "private/svn_wc_private.h" 43156230Smux#include "private/svn_ra_private.h" 44156230Smux 45156230Smux#include "svn_private_config.h" 46156230Smux 47156230Smux 48156230Smux/*** Code. ***/ 49156230Smux 50156230Smux/* Baton for find_undeletables */ 51156230Smuxstruct can_delete_baton_t 52156230Smux{ 53156230Smux const char *root_abspath; 54156230Smux svn_boolean_t target_missing; 55156230Smux}; 56156230Smux 57156230Smux/* An svn_wc_status_func4_t callback function for finding 58156230Smux status structures which are not safely deletable. */ 59156230Smuxstatic svn_error_t * 60156230Smuxfind_undeletables(void *baton, 61156230Smux const char *local_abspath, 62156230Smux const svn_wc_status3_t *status, 63156230Smux apr_pool_t *pool) 64156230Smux{ 65156230Smux if (status->node_status == svn_wc_status_missing) 66156230Smux { 67156230Smux struct can_delete_baton_t *cdt = baton; 68156230Smux 69156230Smux if (strcmp(cdt->root_abspath, local_abspath) == 0) 70156230Smux cdt->target_missing = TRUE; 71156230Smux } 72156230Smux 73156230Smux /* Check for error-ful states. */ 74156230Smux if (status->node_status == svn_wc_status_obstructed) 75156230Smux return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 76156230Smux _("'%s' is in the way of the resource " 77156230Smux "actually under version control"), 78156230Smux svn_dirent_local_style(local_abspath, pool)); 79156230Smux else if (! status->versioned) 80156230Smux return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, 81156230Smux _("'%s' is not under version control"), 82156230Smux svn_dirent_local_style(local_abspath, pool)); 83156230Smux else if ((status->node_status == svn_wc_status_added 84156230Smux || status->node_status == svn_wc_status_replaced) 85156230Smux && status->text_status == svn_wc_status_normal 86156230Smux && (status->prop_status == svn_wc_status_normal 87156230Smux || status->prop_status == svn_wc_status_none)) 88156230Smux { 89156230Smux /* Unmodified copy. Go ahead, remove it */ 90156230Smux } 91 else if (status->node_status != svn_wc_status_normal 92 && status->node_status != svn_wc_status_deleted 93 && status->node_status != svn_wc_status_missing) 94 return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL, 95 _("'%s' has local modifications -- commit or " 96 "revert them first"), 97 svn_dirent_local_style(local_abspath, pool)); 98 99 return SVN_NO_ERROR; 100} 101 102/* Check whether LOCAL_ABSPATH is an external and raise an error if it is. 103 104 A file external should not be deleted since the file external is 105 implemented as a switched file and it would delete the file the 106 file external is switched to, which is not the behavior the user 107 would probably want. 108 109 A directory external should not be deleted since it is the root 110 of a different working copy. */ 111static svn_error_t * 112check_external(const char *local_abspath, 113 svn_client_ctx_t *ctx, 114 apr_pool_t *scratch_pool) 115{ 116 svn_node_kind_t external_kind; 117 const char *defining_abspath; 118 119 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL, 120 NULL, NULL, 121 ctx->wc_ctx, local_abspath, 122 local_abspath, TRUE, 123 scratch_pool, scratch_pool)); 124 125 if (external_kind != svn_node_none) 126 return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL, 127 _("Cannot remove the external at '%s'; " 128 "please edit or delete the svn:externals " 129 "property on '%s'"), 130 svn_dirent_local_style(local_abspath, 131 scratch_pool), 132 svn_dirent_local_style(defining_abspath, 133 scratch_pool)); 134 135 return SVN_NO_ERROR; 136} 137 138/* Verify that the path can be deleted without losing stuff, 139 i.e. ensure that there are no modified or unversioned resources 140 under PATH. This is similar to checking the output of the status 141 command. CTX is used for the client's config options. POOL is 142 used for all temporary allocations. */ 143static svn_error_t * 144can_delete_node(svn_boolean_t *target_missing, 145 const char *local_abspath, 146 svn_client_ctx_t *ctx, 147 apr_pool_t *scratch_pool) 148{ 149 apr_array_header_t *ignores; 150 struct can_delete_baton_t cdt; 151 152 /* Use an infinite-depth status check to see if there's anything in 153 or under PATH which would make it unsafe for deletion. The 154 status callback function find_undeletables() makes the 155 determination, returning an error if it finds anything that shouldn't 156 be deleted. */ 157 158 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool)); 159 160 cdt.root_abspath = local_abspath; 161 cdt.target_missing = FALSE; 162 163 SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, 164 local_abspath, 165 svn_depth_infinity, 166 FALSE /* get_all */, 167 FALSE /* no_ignore */, 168 FALSE /* ignore_text_mod */, 169 ignores, 170 find_undeletables, &cdt, 171 ctx->cancel_func, 172 ctx->cancel_baton, 173 scratch_pool)); 174 175 if (target_missing) 176 *target_missing = cdt.target_missing; 177 178 return SVN_NO_ERROR; 179} 180 181 182static svn_error_t * 183path_driver_cb_func(void **dir_baton, 184 void *parent_baton, 185 void *callback_baton, 186 const char *path, 187 apr_pool_t *pool) 188{ 189 const svn_delta_editor_t *editor = callback_baton; 190 *dir_baton = NULL; 191 return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool); 192} 193 194static svn_error_t * 195single_repos_delete(svn_ra_session_t *ra_session, 196 const char *base_uri, 197 const apr_array_header_t *relpaths, 198 const apr_hash_t *revprop_table, 199 svn_commit_callback2_t commit_callback, 200 void *commit_baton, 201 svn_client_ctx_t *ctx, 202 apr_pool_t *pool) 203{ 204 const svn_delta_editor_t *editor; 205 apr_hash_t *commit_revprops; 206 void *edit_baton; 207 const char *log_msg; 208 int i; 209 svn_error_t *err; 210 211 /* Create new commit items and add them to the array. */ 212 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 213 { 214 svn_client_commit_item3_t *item; 215 const char *tmp_file; 216 apr_array_header_t *commit_items 217 = apr_array_make(pool, relpaths->nelts, sizeof(item)); 218 219 for (i = 0; i < relpaths->nelts; i++) 220 { 221 const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *); 222 223 item = svn_client_commit_item3_create(pool); 224 item->url = svn_path_url_add_component2(base_uri, relpath, pool); 225 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE; 226 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 227 } 228 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, 229 ctx, pool)); 230 if (! log_msg) 231 return SVN_NO_ERROR; 232 } 233 else 234 log_msg = ""; 235 236 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 237 log_msg, ctx, pool)); 238 239 /* Fetch RA commit editor */ 240 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 241 svn_client__get_shim_callbacks(ctx->wc_ctx, 242 NULL, pool))); 243 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 244 commit_revprops, 245 commit_callback, 246 commit_baton, 247 NULL, TRUE, /* No lock tokens */ 248 pool)); 249 250 /* Call the path-based editor driver. */ 251 err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE, 252 path_driver_cb_func, (void *)editor, pool); 253 254 if (err) 255 { 256 return svn_error_trace( 257 svn_error_compose_create(err, 258 editor->abort_edit(edit_baton, pool))); 259 } 260 261 if (ctx->notify_func2) 262 { 263 svn_wc_notify_t *notify; 264 notify = svn_wc_create_notify_url(base_uri, 265 svn_wc_notify_commit_finalizing, 266 pool); 267 ctx->notify_func2(ctx->notify_baton2, notify, pool); 268 } 269 270 /* Close the edit. */ 271 return svn_error_trace(editor->close_edit(edit_baton, pool)); 272} 273 274 275/* Structure for tracking remote delete targets associated with a 276 specific repository. */ 277struct repos_deletables_t 278{ 279 svn_ra_session_t *ra_session; 280 apr_array_header_t *target_uris; 281}; 282 283 284static svn_error_t * 285delete_urls_multi_repos(const apr_array_header_t *uris, 286 const apr_hash_t *revprop_table, 287 svn_commit_callback2_t commit_callback, 288 void *commit_baton, 289 svn_client_ctx_t *ctx, 290 apr_pool_t *pool) 291{ 292 apr_hash_t *deletables = apr_hash_make(pool); 293 apr_pool_t *iterpool; 294 apr_hash_index_t *hi; 295 int i; 296 297 /* Create a hash mapping repository root URLs -> repos_deletables_t * 298 structures. */ 299 for (i = 0; i < uris->nelts; i++) 300 { 301 const char *uri = APR_ARRAY_IDX(uris, i, const char *); 302 struct repos_deletables_t *repos_deletables = NULL; 303 const char *repos_relpath; 304 svn_node_kind_t kind; 305 306 for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi)) 307 { 308 const char *repos_root = apr_hash_this_key(hi); 309 310 repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool); 311 if (repos_relpath) 312 { 313 /* Great! We've found another URI underneath this 314 session. We'll pick out the related RA session for 315 use later, store the new target, and move on. */ 316 repos_deletables = apr_hash_this_val(hi); 317 APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) = 318 apr_pstrdup(pool, uri); 319 break; 320 } 321 } 322 323 /* If we haven't created a repos_deletable structure for this 324 delete target, we need to do. That means opening up an RA 325 session and initializing its targets list. */ 326 if (!repos_deletables) 327 { 328 svn_ra_session_t *ra_session = NULL; 329 const char *repos_root; 330 apr_array_header_t *target_uris; 331 332 /* Open an RA session to (ultimately) the root of the 333 repository in which URI is found. */ 334 SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL, 335 ctx, pool, pool)); 336 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); 337 SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool)); 338 repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool); 339 340 /* Make a new relpaths list for this repository, and add 341 this URI's relpath to it. */ 342 target_uris = apr_array_make(pool, 1, sizeof(const char *)); 343 APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri); 344 345 /* Build our repos_deletables_t item and stash it in the 346 hash. */ 347 repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables)); 348 repos_deletables->ra_session = ra_session; 349 repos_deletables->target_uris = target_uris; 350 svn_hash_sets(deletables, repos_root, repos_deletables); 351 } 352 353 /* If we get here, we should have been able to calculate a 354 repos_relpath for this URI. Let's make sure. (We return an 355 RA error code otherwise for 1.6 compatibility.) */ 356 if (!repos_relpath || !*repos_relpath) 357 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 358 _("URL '%s' not within a repository"), uri); 359 360 /* Now, test to see if the thing actually exists in HEAD. */ 361 SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath, 362 SVN_INVALID_REVNUM, &kind, pool)); 363 if (kind == svn_node_none) 364 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 365 _("URL '%s' does not exist"), uri); 366 } 367 368 /* Now we iterate over the DELETABLES hash, issuing a commit for 369 each repository with its associated collected targets. */ 370 iterpool = svn_pool_create(pool); 371 for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi)) 372 { 373 struct repos_deletables_t *repos_deletables = apr_hash_this_val(hi); 374 const char *base_uri; 375 apr_array_header_t *target_relpaths; 376 377 svn_pool_clear(iterpool); 378 379 /* We want to anchor the commit on the longest common path 380 across the targets for this one repository. If, however, one 381 of our targets is that longest common path, we need instead 382 anchor the commit on that path's immediate parent. Because 383 we're asking svn_uri_condense_targets() to remove 384 redundancies, this situation should be detectable by their 385 being returned either a) only a single, empty-path, target 386 relpath, or b) no target relpaths at all. */ 387 SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths, 388 repos_deletables->target_uris, 389 TRUE, iterpool, iterpool)); 390 SVN_ERR_ASSERT(!svn_path_is_empty(base_uri)); 391 if (target_relpaths->nelts == 0) 392 { 393 const char *target_relpath; 394 395 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool); 396 APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath; 397 } 398 else if ((target_relpaths->nelts == 1) 399 && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0, 400 const char *)))) 401 { 402 const char *target_relpath; 403 404 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool); 405 APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath; 406 } 407 408 SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool)); 409 SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri, 410 target_relpaths, 411 revprop_table, commit_callback, 412 commit_baton, ctx, iterpool)); 413 } 414 svn_pool_destroy(iterpool); 415 416 return SVN_NO_ERROR; 417} 418 419svn_error_t * 420svn_client__wc_delete(const char *local_abspath, 421 svn_boolean_t force, 422 svn_boolean_t dry_run, 423 svn_boolean_t keep_local, 424 svn_wc_notify_func2_t notify_func, 425 void *notify_baton, 426 svn_client_ctx_t *ctx, 427 apr_pool_t *pool) 428{ 429 svn_boolean_t target_missing = FALSE; 430 431 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 432 433 SVN_ERR(check_external(local_abspath, ctx, pool)); 434 435 if (!force && !keep_local) 436 /* Verify that there are no "awkward" files */ 437 SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool)); 438 439 if (!dry_run) 440 /* Mark the entry for commit deletion and perform wc deletion */ 441 return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath, 442 keep_local || target_missing 443 /*keep_local */, 444 TRUE /* delete_unversioned */, 445 ctx->cancel_func, ctx->cancel_baton, 446 notify_func, notify_baton, pool)); 447 448 return SVN_NO_ERROR; 449} 450 451svn_error_t * 452svn_client__wc_delete_many(const apr_array_header_t *targets, 453 svn_boolean_t force, 454 svn_boolean_t dry_run, 455 svn_boolean_t keep_local, 456 svn_wc_notify_func2_t notify_func, 457 void *notify_baton, 458 svn_client_ctx_t *ctx, 459 apr_pool_t *pool) 460{ 461 int i; 462 svn_boolean_t has_non_missing = FALSE; 463 464 for (i = 0; i < targets->nelts; i++) 465 { 466 const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *); 467 468 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 469 470 SVN_ERR(check_external(local_abspath, ctx, pool)); 471 472 if (!force && !keep_local) 473 { 474 svn_boolean_t missing; 475 /* Verify that there are no "awkward" files */ 476 477 SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool)); 478 479 if (! missing) 480 has_non_missing = TRUE; 481 } 482 else 483 has_non_missing = TRUE; 484 } 485 486 if (!dry_run) 487 { 488 /* Mark the entry for commit deletion and perform wc deletion */ 489 490 /* If none of the targets exists, pass keep local TRUE, to avoid 491 deleting case-different files. Detecting this in the generic case 492 from the delete code is expensive */ 493 return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets, 494 keep_local || !has_non_missing, 495 TRUE /* delete_unversioned_target */, 496 ctx->cancel_func, 497 ctx->cancel_baton, 498 notify_func, notify_baton, 499 pool)); 500 } 501 502 return SVN_NO_ERROR; 503} 504 505svn_error_t * 506svn_client_delete4(const apr_array_header_t *paths, 507 svn_boolean_t force, 508 svn_boolean_t keep_local, 509 const apr_hash_t *revprop_table, 510 svn_commit_callback2_t commit_callback, 511 void *commit_baton, 512 svn_client_ctx_t *ctx, 513 apr_pool_t *pool) 514{ 515 svn_boolean_t is_url; 516 517 if (! paths->nelts) 518 return SVN_NO_ERROR; 519 520 SVN_ERR(svn_client__assert_homogeneous_target_type(paths)); 521 is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *)); 522 523 if (is_url) 524 { 525 SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback, 526 commit_baton, ctx, pool)); 527 } 528 else 529 { 530 const char *local_abspath; 531 apr_hash_t *wcroots; 532 apr_hash_index_t *hi; 533 int i; 534 int j; 535 apr_pool_t *iterpool; 536 svn_boolean_t is_new_target; 537 538 /* Build a map of wcroots and targets within them. */ 539 wcroots = apr_hash_make(pool); 540 iterpool = svn_pool_create(pool); 541 for (i = 0; i < paths->nelts; i++) 542 { 543 const char *wcroot_abspath; 544 apr_array_header_t *targets; 545 546 svn_pool_clear(iterpool); 547 548 /* See if the user wants us to stop. */ 549 if (ctx->cancel_func) 550 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 551 552 SVN_ERR(svn_dirent_get_absolute(&local_abspath, 553 APR_ARRAY_IDX(paths, i, 554 const char *), 555 pool)); 556 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, 557 local_abspath, pool, iterpool)); 558 targets = svn_hash_gets(wcroots, wcroot_abspath); 559 if (targets == NULL) 560 { 561 targets = apr_array_make(pool, 1, sizeof(const char *)); 562 svn_hash_sets(wcroots, wcroot_abspath, targets); 563 } 564 565 /* Make sure targets are unique. */ 566 is_new_target = TRUE; 567 for (j = 0; j < targets->nelts; j++) 568 { 569 if (strcmp(APR_ARRAY_IDX(targets, j, const char *), 570 local_abspath) == 0) 571 { 572 is_new_target = FALSE; 573 break; 574 } 575 } 576 577 if (is_new_target) 578 APR_ARRAY_PUSH(targets, const char *) = local_abspath; 579 } 580 581 /* Delete the targets from each working copy in turn. */ 582 for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi)) 583 { 584 const char *root_abspath; 585 const apr_array_header_t *targets = apr_hash_this_val(hi); 586 587 svn_pool_clear(iterpool); 588 589 SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets, 590 FALSE, iterpool, iterpool)); 591 592 SVN_WC__CALL_WITH_WRITE_LOCK( 593 svn_client__wc_delete_many(targets, force, FALSE, keep_local, 594 ctx->notify_func2, ctx->notify_baton2, 595 ctx, iterpool), 596 ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */, 597 iterpool); 598 } 599 svn_pool_destroy(iterpool); 600 } 601 602 return SVN_NO_ERROR; 603} 604