1/* 2 * delete.c: Handling of the in-wc side of the delete operation 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <string.h> 27#include <stdlib.h> 28 29#include <apr_pools.h> 30 31#include "svn_types.h" 32#include "svn_pools.h" 33#include "svn_string.h" 34#include "svn_error.h" 35#include "svn_dirent_uri.h" 36#include "svn_wc.h" 37#include "svn_io.h" 38 39#include "wc.h" 40#include "adm_files.h" 41#include "conflicts.h" 42#include "workqueue.h" 43 44#include "svn_private_config.h" 45#include "private/svn_wc_private.h" 46 47 48/* Remove/erase PATH from the working copy. This involves deleting PATH 49 * from the physical filesystem. PATH is assumed to be an unversioned file 50 * or directory. 51 * 52 * If ignore_enoent is TRUE, ignore missing targets. 53 * 54 * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various 55 * points, return any error immediately. 56 */ 57static svn_error_t * 58erase_unversioned_from_wc(const char *path, 59 svn_boolean_t ignore_enoent, 60 svn_cancel_func_t cancel_func, 61 void *cancel_baton, 62 apr_pool_t *scratch_pool) 63{ 64 svn_error_t *err; 65 66 /* Optimize the common case: try to delete the file */ 67 err = svn_io_remove_file2(path, ignore_enoent, scratch_pool); 68 if (err) 69 { 70 /* Then maybe it was a directory? */ 71 svn_error_clear(err); 72 73 err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton, 74 scratch_pool); 75 76 if (err) 77 { 78 /* We're unlikely to end up here. But we need this fallback 79 to make sure we report the right error *and* try the 80 correct deletion at least once. */ 81 svn_node_kind_t kind; 82 83 svn_error_clear(err); 84 SVN_ERR(svn_io_check_path(path, &kind, scratch_pool)); 85 if (kind == svn_node_file) 86 SVN_ERR(svn_io_remove_file2(path, ignore_enoent, scratch_pool)); 87 else if (kind == svn_node_dir) 88 SVN_ERR(svn_io_remove_dir2(path, ignore_enoent, 89 cancel_func, cancel_baton, 90 scratch_pool)); 91 else if (kind == svn_node_none) 92 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, 93 _("'%s' does not exist"), 94 svn_dirent_local_style(path, 95 scratch_pool)); 96 else 97 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 98 _("Unsupported node kind for path '%s'"), 99 svn_dirent_local_style(path, 100 scratch_pool)); 101 102 } 103 } 104 105 return SVN_NO_ERROR; 106} 107 108/* Helper for svn_wc__delete and svn_wc__delete_many */ 109static svn_error_t * 110create_delete_wq_items(svn_skel_t **work_items, 111 svn_wc__db_t *db, 112 const char *local_abspath, 113 svn_node_kind_t kind, 114 svn_boolean_t conflicted, 115 apr_pool_t *result_pool, 116 apr_pool_t *scratch_pool) 117{ 118 *work_items = NULL; 119 120 /* Schedule the on-disk delete */ 121 if (kind == svn_node_dir) 122 SVN_ERR(svn_wc__wq_build_dir_remove(work_items, db, local_abspath, 123 local_abspath, 124 TRUE /* recursive */, 125 result_pool, scratch_pool)); 126 else 127 SVN_ERR(svn_wc__wq_build_file_remove(work_items, db, local_abspath, 128 local_abspath, 129 result_pool, scratch_pool)); 130 131 /* Read conflicts, to allow deleting the markers after updating the DB */ 132 if (conflicted) 133 { 134 svn_skel_t *conflict; 135 const apr_array_header_t *markers; 136 int i; 137 138 SVN_ERR(svn_wc__db_read_conflict(&conflict, db, local_abspath, 139 scratch_pool, scratch_pool)); 140 141 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath, 142 conflict, 143 scratch_pool, scratch_pool)); 144 145 /* Maximum number of markers is 4, so no iterpool */ 146 for (i = 0; markers && i < markers->nelts; i++) 147 { 148 const char *marker_abspath; 149 svn_node_kind_t marker_kind; 150 151 marker_abspath = APR_ARRAY_IDX(markers, i, const char *); 152 SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind, 153 scratch_pool)); 154 155 if (marker_kind == svn_node_file) 156 { 157 svn_skel_t *work_item; 158 159 SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, 160 local_abspath, 161 marker_abspath, 162 result_pool, 163 scratch_pool)); 164 165 *work_items = svn_wc__wq_merge(*work_items, work_item, 166 result_pool); 167 } 168 } 169 } 170 171 return SVN_NO_ERROR; 172} 173 174svn_error_t * 175svn_wc__delete_many(svn_wc_context_t *wc_ctx, 176 const apr_array_header_t *targets, 177 svn_boolean_t keep_local, 178 svn_boolean_t delete_unversioned_target, 179 svn_cancel_func_t cancel_func, 180 void *cancel_baton, 181 svn_wc_notify_func2_t notify_func, 182 void *notify_baton, 183 apr_pool_t *scratch_pool) 184{ 185 svn_wc__db_t *db = wc_ctx->db; 186 svn_error_t *err; 187 svn_wc__db_status_t status; 188 svn_node_kind_t kind; 189 svn_skel_t *work_items = NULL; 190 apr_array_header_t *versioned_targets; 191 const char *local_abspath; 192 int i; 193 apr_pool_t *iterpool; 194 195 iterpool = svn_pool_create(scratch_pool); 196 versioned_targets = apr_array_make(scratch_pool, targets->nelts, 197 sizeof(const char *)); 198 for (i = 0; i < targets->nelts; i++) 199 { 200 svn_boolean_t conflicted = FALSE; 201 const char *repos_relpath; 202 203 svn_pool_clear(iterpool); 204 205 local_abspath = APR_ARRAY_IDX(targets, i, const char *); 206 err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, 207 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 208 NULL, NULL, NULL, NULL, NULL, NULL, NULL, 209 NULL, &conflicted, 210 NULL, NULL, NULL, NULL, NULL, NULL, 211 db, local_abspath, iterpool, iterpool); 212 213 if (err) 214 { 215 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 216 { 217 svn_error_clear(err); 218 if (delete_unversioned_target && !keep_local) 219 SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, 220 cancel_func, cancel_baton, 221 iterpool)); 222 continue; 223 } 224 else 225 return svn_error_trace(err); 226 } 227 228 APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath; 229 230 switch (status) 231 { 232 /* svn_wc__db_status_server_excluded handled by 233 * svn_wc__db_op_delete_many */ 234 case svn_wc__db_status_excluded: 235 case svn_wc__db_status_not_present: 236 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 237 _("'%s' cannot be deleted"), 238 svn_dirent_local_style(local_abspath, 239 iterpool)); 240 241 /* Explicitly ignore other statii */ 242 default: 243 break; 244 } 245 246 if (status == svn_wc__db_status_normal 247 && kind == svn_node_dir) 248 { 249 svn_boolean_t is_wcroot; 250 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, 251 iterpool)); 252 253 if (is_wcroot) 254 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 255 _("'%s' is the root of a working copy and " 256 "cannot be deleted"), 257 svn_dirent_local_style(local_abspath, 258 iterpool)); 259 } 260 if (repos_relpath && !repos_relpath[0]) 261 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 262 _("'%s' represents the repository root " 263 "and cannot be deleted"), 264 svn_dirent_local_style(local_abspath, 265 iterpool)); 266 267 /* Verify if we have a write lock on the parent of this node as we might 268 be changing the childlist of that directory. */ 269 SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, 270 iterpool), 271 iterpool)); 272 273 /* Prepare the on-disk delete */ 274 if (!keep_local) 275 { 276 svn_skel_t *work_item; 277 278 SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind, 279 conflicted, 280 scratch_pool, iterpool)); 281 282 work_items = svn_wc__wq_merge(work_items, work_item, 283 scratch_pool); 284 } 285 } 286 287 if (versioned_targets->nelts == 0) 288 return SVN_NO_ERROR; 289 290 SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets, 291 !keep_local /* delete_dir_externals */, 292 work_items, 293 cancel_func, cancel_baton, 294 notify_func, notify_baton, 295 iterpool)); 296 297 if (work_items != NULL) 298 { 299 /* Our only caller locked the wc, so for now assume it only passed 300 nodes from a single wc (asserted in svn_wc__db_op_delete_many) */ 301 local_abspath = APR_ARRAY_IDX(versioned_targets, 0, const char *); 302 303 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 304 iterpool)); 305 } 306 svn_pool_destroy(iterpool); 307 308 return SVN_NO_ERROR; 309} 310 311svn_error_t * 312svn_wc_delete4(svn_wc_context_t *wc_ctx, 313 const char *local_abspath, 314 svn_boolean_t keep_local, 315 svn_boolean_t delete_unversioned_target, 316 svn_cancel_func_t cancel_func, 317 void *cancel_baton, 318 svn_wc_notify_func2_t notify_func, 319 void *notify_baton, 320 apr_pool_t *scratch_pool) 321{ 322 apr_pool_t *pool = scratch_pool; 323 svn_wc__db_t *db = wc_ctx->db; 324 svn_error_t *err; 325 svn_wc__db_status_t status; 326 svn_node_kind_t kind; 327 svn_boolean_t conflicted; 328 svn_skel_t *work_items = NULL; 329 const char *repos_relpath; 330 331 err = svn_wc__db_read_info(&status, &kind, NULL, &repos_relpath, NULL, NULL, 332 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 333 NULL, NULL, NULL, NULL, NULL, NULL, &conflicted, 334 NULL, NULL, NULL, NULL, NULL, NULL, 335 db, local_abspath, pool, pool); 336 337 if (delete_unversioned_target && 338 err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 339 { 340 svn_error_clear(err); 341 342 if (!keep_local) 343 SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE, 344 cancel_func, cancel_baton, 345 pool)); 346 return SVN_NO_ERROR; 347 } 348 else 349 SVN_ERR(err); 350 351 switch (status) 352 { 353 /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */ 354 case svn_wc__db_status_excluded: 355 case svn_wc__db_status_not_present: 356 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 357 _("'%s' cannot be deleted"), 358 svn_dirent_local_style(local_abspath, pool)); 359 360 /* Explicitly ignore other statii */ 361 default: 362 break; 363 } 364 365 if (status == svn_wc__db_status_normal 366 && kind == svn_node_dir) 367 { 368 svn_boolean_t is_wcroot; 369 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool)); 370 371 if (is_wcroot) 372 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 373 _("'%s' is the root of a working copy and " 374 "cannot be deleted"), 375 svn_dirent_local_style(local_abspath, pool)); 376 } 377 if (repos_relpath && !repos_relpath[0]) 378 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 379 _("'%s' represents the repository root " 380 "and cannot be deleted"), 381 svn_dirent_local_style(local_abspath, pool)); 382 383 /* Verify if we have a write lock on the parent of this node as we might 384 be changing the childlist of that directory. */ 385 SVN_ERR(svn_wc__write_check(db, svn_dirent_dirname(local_abspath, pool), 386 pool)); 387 388 /* Prepare the on-disk delete */ 389 if (!keep_local) 390 { 391 SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind, 392 conflicted, 393 scratch_pool, scratch_pool)); 394 } 395 396 SVN_ERR(svn_wc__db_op_delete(db, local_abspath, 397 NULL /*moved_to_abspath */, 398 !keep_local /* delete_dir_externals */, 399 NULL, work_items, 400 cancel_func, cancel_baton, 401 notify_func, notify_baton, 402 pool)); 403 404 if (work_items) 405 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton, 406 scratch_pool)); 407 408 return SVN_NO_ERROR; 409} 410 411svn_error_t * 412svn_wc__internal_remove_from_revision_control(svn_wc__db_t *db, 413 const char *local_abspath, 414 svn_boolean_t destroy_wf, 415 svn_cancel_func_t cancel_func, 416 void *cancel_baton, 417 apr_pool_t *scratch_pool) 418{ 419 svn_boolean_t left_something = FALSE; 420 svn_boolean_t is_root; 421 svn_error_t *err = NULL; 422 423 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool)); 424 425 SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath 426 : svn_dirent_dirname(local_abspath, 427 scratch_pool), 428 scratch_pool)); 429 430 SVN_ERR(svn_wc__db_op_remove_node(&left_something, 431 db, local_abspath, 432 destroy_wf /* destroy_wc */, 433 destroy_wf /* destroy_changes */, 434 SVN_INVALID_REVNUM, 435 svn_wc__db_status_not_present, 436 svn_node_none, 437 NULL, NULL, 438 cancel_func, cancel_baton, 439 scratch_pool)); 440 441 SVN_ERR(svn_wc__wq_run(db, local_abspath, 442 cancel_func, cancel_baton, 443 scratch_pool)); 444 445 if (is_root) 446 { 447 /* Destroy the administrative area */ 448 SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton, 449 scratch_pool)); 450 451 /* And if we didn't leave something interesting, remove the directory */ 452 if (!left_something && destroy_wf) 453 err = svn_io_dir_remove_nonrecursive(local_abspath, scratch_pool); 454 } 455 456 if (left_something || err) 457 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL); 458 459 return SVN_NO_ERROR; 460} 461 462/* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */ 463static svn_error_t * 464remove_from_revision_status_callback(void *baton, 465 const char *local_abspath, 466 const svn_wc_status3_t *status, 467 apr_pool_t *scratch_pool) 468{ 469 /* For legacy reasons we only check the file contents for changes */ 470 if (status->versioned 471 && status->kind == svn_node_file 472 && (status->text_status == svn_wc_status_modified 473 || status->text_status == svn_wc_status_conflicted)) 474 { 475 return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD, NULL, 476 _("File '%s' has local modifications"), 477 svn_dirent_local_style(local_abspath, 478 scratch_pool)); 479 } 480 return SVN_NO_ERROR; 481} 482 483svn_error_t * 484svn_wc_remove_from_revision_control2(svn_wc_context_t *wc_ctx, 485 const char *local_abspath, 486 svn_boolean_t destroy_wf, 487 svn_boolean_t instant_error, 488 svn_cancel_func_t cancel_func, 489 void *cancel_baton, 490 apr_pool_t *scratch_pool) 491{ 492 if (instant_error) 493 { 494 SVN_ERR(svn_wc_walk_status(wc_ctx, local_abspath, svn_depth_infinity, 495 FALSE, FALSE, FALSE, NULL, 496 remove_from_revision_status_callback, NULL, 497 cancel_func, cancel_baton, 498 scratch_pool)); 499 } 500 return svn_error_trace( 501 svn_wc__internal_remove_from_revision_control(wc_ctx->db, 502 local_abspath, 503 destroy_wf, 504 cancel_func, 505 cancel_baton, 506 scratch_pool)); 507} 508 509