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 */
57 static svn_error_t *
erase_unversioned_from_wc(const char * path,svn_boolean_t ignore_enoent,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)58 erase_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 */
109 static svn_error_t *
create_delete_wq_items(svn_skel_t ** work_items,svn_wc__db_t * db,const char * local_abspath,svn_node_kind_t kind,svn_boolean_t conflicted,apr_pool_t * result_pool,apr_pool_t * scratch_pool)110 create_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
174 svn_error_t *
svn_wc__delete_many(svn_wc_context_t * wc_ctx,const apr_array_header_t * targets,svn_boolean_t keep_local,svn_boolean_t delete_unversioned_target,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)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,
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
311 svn_error_t *
svn_wc_delete4(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t keep_local,svn_boolean_t delete_unversioned_target,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)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,
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
411 svn_error_t *
svn_wc__internal_remove_from_revision_control(svn_wc__db_t * db,const char * local_abspath,svn_boolean_t destroy_wf,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)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,
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 */
463 static svn_error_t *
remove_from_revision_status_callback(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)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)
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
483 svn_error_t *
svn_wc_remove_from_revision_control2(svn_wc_context_t * wc_ctx,const char * local_abspath,svn_boolean_t destroy_wf,svn_boolean_t instant_error,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)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,
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