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
, NULL
, NULL
,
140 scratch_pool
, scratch_pool
));
142 SVN_ERR(svn_wc__conflict_read_markers(&markers
, db
, local_abspath
,
144 scratch_pool
, scratch_pool
));
146 /* Maximum number of markers is 4, so no iterpool */
147 for (i
= 0; markers
&& i
< markers
->nelts
; i
++)
149 const char *marker_abspath
;
150 svn_node_kind_t marker_kind
;
152 marker_abspath
= APR_ARRAY_IDX(markers
, i
, const char *);
153 SVN_ERR(svn_io_check_path(marker_abspath
, &marker_kind
,
156 if (marker_kind
== svn_node_file
)
158 svn_skel_t
*work_item
;
160 SVN_ERR(svn_wc__wq_build_file_remove(&work_item
, db
,
166 *work_items
= svn_wc__wq_merge(*work_items
, work_item
,
176 svn_wc__delete_many(svn_wc_context_t
*wc_ctx
,
177 const apr_array_header_t
*targets
,
178 svn_boolean_t keep_local
,
179 svn_boolean_t delete_unversioned_target
,
180 svn_cancel_func_t cancel_func
,
182 svn_wc_notify_func2_t notify_func
,
184 apr_pool_t
*scratch_pool
)
186 svn_wc__db_t
*db
= wc_ctx
->db
;
188 svn_wc__db_status_t status
;
189 svn_node_kind_t kind
;
190 svn_skel_t
*work_items
= NULL
;
191 apr_array_header_t
*versioned_targets
;
192 const char *local_abspath
;
194 apr_pool_t
*iterpool
;
196 iterpool
= svn_pool_create(scratch_pool
);
197 versioned_targets
= apr_array_make(scratch_pool
, targets
->nelts
,
198 sizeof(const char *));
199 for (i
= 0; i
< targets
->nelts
; i
++)
201 svn_boolean_t conflicted
= FALSE
;
202 const char *repos_relpath
;
204 svn_pool_clear(iterpool
);
206 local_abspath
= APR_ARRAY_IDX(targets
, i
, const char *);
207 err
= svn_wc__db_read_info(&status
, &kind
, NULL
, &repos_relpath
, NULL
,
208 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
209 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
211 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
212 db
, local_abspath
, iterpool
, iterpool
);
216 if (err
->apr_err
== SVN_ERR_WC_PATH_NOT_FOUND
)
218 svn_error_clear(err
);
219 if (delete_unversioned_target
&& !keep_local
)
220 SVN_ERR(erase_unversioned_from_wc(local_abspath
, FALSE
,
221 cancel_func
, cancel_baton
,
226 return svn_error_trace(err
);
229 APR_ARRAY_PUSH(versioned_targets
, const char *) = local_abspath
;
233 /* svn_wc__db_status_server_excluded handled by
234 * svn_wc__db_op_delete_many */
235 case svn_wc__db_status_excluded
:
236 case svn_wc__db_status_not_present
:
237 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND
, NULL
,
238 _("'%s' cannot be deleted"),
239 svn_dirent_local_style(local_abspath
,
242 /* Explicitly ignore other statii */
247 if (status
== svn_wc__db_status_normal
248 && kind
== svn_node_dir
)
250 svn_boolean_t is_wcroot
;
251 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot
, db
, local_abspath
,
255 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS
, NULL
,
256 _("'%s' is the root of a working copy and "
257 "cannot be deleted"),
258 svn_dirent_local_style(local_abspath
,
261 if (repos_relpath
&& !repos_relpath
[0])
262 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS
, NULL
,
263 _("'%s' represents the repository root "
264 "and cannot be deleted"),
265 svn_dirent_local_style(local_abspath
,
268 /* Verify if we have a write lock on the parent of this node as we might
269 be changing the childlist of that directory. */
270 SVN_ERR(svn_wc__write_check(db
, svn_dirent_dirname(local_abspath
,
274 /* Prepare the on-disk delete */
277 svn_skel_t
*work_item
;
279 SVN_ERR(create_delete_wq_items(&work_item
, db
, local_abspath
, kind
,
281 scratch_pool
, iterpool
));
283 work_items
= svn_wc__wq_merge(work_items
, work_item
,
288 if (versioned_targets
->nelts
== 0)
291 SVN_ERR(svn_wc__db_op_delete_many(db
, versioned_targets
,
292 !keep_local
/* delete_dir_externals */,
294 cancel_func
, cancel_baton
,
295 notify_func
, notify_baton
,
298 if (work_items
!= NULL
)
300 /* Our only caller locked the wc, so for now assume it only passed
301 nodes from a single wc (asserted in svn_wc__db_op_delete_many) */
302 local_abspath
= APR_ARRAY_IDX(versioned_targets
, 0, const char *);
304 SVN_ERR(svn_wc__wq_run(db
, local_abspath
, cancel_func
, cancel_baton
,
307 svn_pool_destroy(iterpool
);
313 svn_wc_delete4(svn_wc_context_t
*wc_ctx
,
314 const char *local_abspath
,
315 svn_boolean_t keep_local
,
316 svn_boolean_t delete_unversioned_target
,
317 svn_cancel_func_t cancel_func
,
319 svn_wc_notify_func2_t notify_func
,
321 apr_pool_t
*scratch_pool
)
323 apr_pool_t
*pool
= scratch_pool
;
324 svn_wc__db_t
*db
= wc_ctx
->db
;
326 svn_wc__db_status_t status
;
327 svn_node_kind_t kind
;
328 svn_boolean_t conflicted
;
329 svn_skel_t
*work_items
= NULL
;
330 const char *repos_relpath
;
332 err
= svn_wc__db_read_info(&status
, &kind
, NULL
, &repos_relpath
, NULL
, NULL
,
333 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
334 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, &conflicted
,
335 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
336 db
, local_abspath
, pool
, pool
);
338 if (delete_unversioned_target
&&
339 err
!= NULL
&& err
->apr_err
== SVN_ERR_WC_PATH_NOT_FOUND
)
341 svn_error_clear(err
);
344 SVN_ERR(erase_unversioned_from_wc(local_abspath
, FALSE
,
345 cancel_func
, cancel_baton
,
354 /* svn_wc__db_status_server_excluded handled by svn_wc__db_op_delete */
355 case svn_wc__db_status_excluded
:
356 case svn_wc__db_status_not_present
:
357 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND
, NULL
,
358 _("'%s' cannot be deleted"),
359 svn_dirent_local_style(local_abspath
, pool
));
361 /* Explicitly ignore other statii */
366 if (status
== svn_wc__db_status_normal
367 && kind
== svn_node_dir
)
369 svn_boolean_t is_wcroot
;
370 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot
, db
, local_abspath
, pool
));
373 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS
, NULL
,
374 _("'%s' is the root of a working copy and "
375 "cannot be deleted"),
376 svn_dirent_local_style(local_abspath
, pool
));
378 if (repos_relpath
&& !repos_relpath
[0])
379 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS
, NULL
,
380 _("'%s' represents the repository root "
381 "and cannot be deleted"),
382 svn_dirent_local_style(local_abspath
, pool
));
384 /* Verify if we have a write lock on the parent of this node as we might
385 be changing the childlist of that directory. */
386 SVN_ERR(svn_wc__write_check(db
, svn_dirent_dirname(local_abspath
, pool
),
389 /* Prepare the on-disk delete */
392 SVN_ERR(create_delete_wq_items(&work_items
, db
, local_abspath
, kind
,
394 scratch_pool
, scratch_pool
));
397 SVN_ERR(svn_wc__db_op_delete(db
, local_abspath
,
398 NULL
/*moved_to_abspath */,
399 !keep_local
/* delete_dir_externals */,
401 cancel_func
, cancel_baton
,
402 notify_func
, notify_baton
,
406 SVN_ERR(svn_wc__wq_run(db
, local_abspath
, cancel_func
, cancel_baton
,
413 svn_wc__internal_remove_from_revision_control(svn_wc__db_t
*db
,
414 const char *local_abspath
,
415 svn_boolean_t destroy_wf
,
416 svn_cancel_func_t cancel_func
,
418 apr_pool_t
*scratch_pool
)
420 svn_boolean_t left_something
= FALSE
;
421 svn_boolean_t is_root
;
422 svn_error_t
*err
= NULL
;
424 SVN_ERR(svn_wc__db_is_wcroot(&is_root
, db
, local_abspath
, scratch_pool
));
426 SVN_ERR(svn_wc__write_check(db
, is_root
? local_abspath
427 : svn_dirent_dirname(local_abspath
,
431 SVN_ERR(svn_wc__db_op_remove_node(&left_something
,
433 destroy_wf
/* destroy_wc */,
434 destroy_wf
/* destroy_changes */,
436 cancel_func
, cancel_baton
,
439 SVN_ERR(svn_wc__wq_run(db
, local_abspath
,
440 cancel_func
, cancel_baton
,
445 /* Destroy the administrative area */
446 SVN_ERR(svn_wc__adm_destroy(db
, local_abspath
, cancel_func
, cancel_baton
,
449 /* And if we didn't leave something interesting, remove the directory */
450 if (!left_something
&& destroy_wf
)
451 err
= svn_io_dir_remove_nonrecursive(local_abspath
, scratch_pool
);
454 if (left_something
|| err
)
455 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD
, err
, NULL
);
460 /* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */
462 remove_from_revision_status_callback(void *baton
,
463 const char *local_abspath
,
464 const svn_wc_status3_t
*status
,
465 apr_pool_t
*scratch_pool
)
467 /* For legacy reasons we only check the file contents for changes */
468 if (status
->versioned
469 && status
->kind
== svn_node_file
470 && (status
->text_status
== svn_wc_status_modified
471 || status
->text_status
== svn_wc_status_conflicted
))
473 return svn_error_createf(SVN_ERR_WC_LEFT_LOCAL_MOD
, NULL
,
474 _("File '%s' has local modifications"),
475 svn_dirent_local_style(local_abspath
,
482 svn_wc_remove_from_revision_control2(svn_wc_context_t
*wc_ctx
,
483 const char *local_abspath
,
484 svn_boolean_t destroy_wf
,
485 svn_boolean_t instant_error
,
486 svn_cancel_func_t cancel_func
,
488 apr_pool_t
*scratch_pool
)
492 SVN_ERR(svn_wc_walk_status(wc_ctx
, local_abspath
, svn_depth_infinity
,
493 FALSE
, FALSE
, FALSE
, NULL
,
494 remove_from_revision_status_callback
, NULL
,
495 cancel_func
, cancel_baton
,
498 return svn_error_trace(
499 svn_wc__internal_remove_from_revision_control(wc_ctx
->db
,