2 * delete.c: Handling of the in-wc side of the delete operation
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
29 #include <apr_pools.h>
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"
40 #include "adm_files.h"
41 #include "conflicts.h"
42 #include "workqueue.h"
44 #include "svn_private_config.h"
45 #include "private/svn_wc_private.h"
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
52 * If ignore_enoent is TRUE, ignore missing targets.
54 * If CANCEL_FUNC is non-null, invoke it with CANCEL_BATON at various
55 * points, return any error immediately.
58 erase_unversioned_from_wc(const char *path,
59 svn_boolean_t ignore_enoent,
60 svn_cancel_func_t cancel_func,
62 apr_pool_t *scratch_pool)
66 /* Optimize the common case: try to delete the file */
67 err = svn_io_remove_file2(path, ignore_enoent, scratch_pool);
70 /* Then maybe it was a directory? */
73 err = svn_io_remove_dir2(path, ignore_enoent, cancel_func, cancel_baton,
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. */
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,
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,
97 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
98 _("Unsupported node kind for path '%s'"),
99 svn_dirent_local_style(path,
108 /* Helper for svn_wc__delete and svn_wc__delete_many */
110 create_delete_wq_items(svn_skel_t **work_items,
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)
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,
124 TRUE /* recursive */,
125 result_pool, scratch_pool));
127 SVN_ERR(svn_wc__wq_build_file_remove(work_items, db, local_abspath,
129 result_pool, scratch_pool));
131 /* Read conflicts, to allow deleting the markers after updating the DB */
134 svn_skel_t *conflict;
135 const apr_array_header_t *markers;
138 SVN_ERR(svn_wc__db_read_conflict(&conflict, db, local_abspath,
139 scratch_pool, scratch_pool));
141 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath,
143 scratch_pool, scratch_pool));
145 /* Maximum number of markers is 4, so no iterpool */
146 for (i = 0; markers && i < markers->nelts; i++)
148 const char *marker_abspath;
149 svn_node_kind_t marker_kind;
151 marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
152 SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind,
155 if (marker_kind == svn_node_file)
157 svn_skel_t *work_item;
159 SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db,
165 *work_items = svn_wc__wq_merge(*work_items, work_item,
175 svn_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,
181 svn_wc_notify_func2_t notify_func,
183 apr_pool_t *scratch_pool)
185 svn_wc__db_t *db = wc_ctx->db;
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;
193 apr_pool_t *iterpool;
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++)
200 svn_boolean_t conflicted = FALSE;
201 const char *repos_relpath;
203 svn_pool_clear(iterpool);
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,
210 NULL, NULL, NULL, NULL, NULL, NULL,
211 db, local_abspath, iterpool, iterpool);
215 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
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,
225 return svn_error_trace(err);
228 APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath;
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,
241 /* Explicitly ignore other statii */
246 if (status == svn_wc__db_status_normal
247 && kind == svn_node_dir)
249 svn_boolean_t is_wcroot;
250 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
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,
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,
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,
273 /* Prepare the on-disk delete */
276 svn_skel_t *work_item;
278 SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind,
280 scratch_pool, iterpool));
282 work_items = svn_wc__wq_merge(work_items, work_item,
287 if (versioned_targets->nelts == 0)
290 SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets,
291 !keep_local /* delete_dir_externals */,
293 cancel_func, cancel_baton,
294 notify_func, notify_baton,
297 if (work_items != NULL)
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 *);
303 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
306 svn_pool_destroy(iterpool);
312 svn_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,
318 svn_wc_notify_func2_t notify_func,
320 apr_pool_t *scratch_pool)
322 apr_pool_t *pool = scratch_pool;
323 svn_wc__db_t *db = wc_ctx->db;
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;
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);
337 if (delete_unversioned_target &&
338 err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
340 svn_error_clear(err);
343 SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE,
344 cancel_func, cancel_baton,
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));
360 /* Explicitly ignore other statii */
365 if (status == svn_wc__db_status_normal
366 && kind == svn_node_dir)
368 svn_boolean_t is_wcroot;
369 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool));
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));
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));
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),
388 /* Prepare the on-disk delete */
391 SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind,
393 scratch_pool, scratch_pool));
396 SVN_ERR(svn_wc__db_op_delete(db, local_abspath,
397 NULL /*moved_to_abspath */,
398 !keep_local /* delete_dir_externals */,
400 cancel_func, cancel_baton,
401 notify_func, notify_baton,
405 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
412 svn_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,
417 apr_pool_t *scratch_pool)
419 svn_boolean_t left_something = FALSE;
420 svn_boolean_t is_root;
421 svn_error_t *err = NULL;
423 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
425 SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath
426 : svn_dirent_dirname(local_abspath,
430 SVN_ERR(svn_wc__db_op_remove_node(&left_something,
432 destroy_wf /* destroy_wc */,
433 destroy_wf /* destroy_changes */,
435 svn_wc__db_status_not_present,
438 cancel_func, cancel_baton,
441 SVN_ERR(svn_wc__wq_run(db, local_abspath,
442 cancel_func, cancel_baton,
447 /* Destroy the administrative area */
448 SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton,
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);
456 if (left_something || err)
457 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL);
462 /* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */
464 remove_from_revision_status_callback(void *baton,
465 const char *local_abspath,
466 const svn_wc_status3_t *status,
467 apr_pool_t *scratch_pool)
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))
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,
484 svn_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,
490 apr_pool_t *scratch_pool)
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,
500 return svn_error_trace(
501 svn_wc__internal_remove_from_revision_control(wc_ctx->db,