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, NULL, NULL,
139 db, local_abspath,
140 scratch_pool, scratch_pool));
141
142 SVN_ERR(svn_wc__conflict_read_markers(&markers, db, local_abspath,
143 conflict,
144 scratch_pool, scratch_pool));
145
146 /* Maximum number of markers is 4, so no iterpool */
147 for (i = 0; markers && i < markers->nelts; i++)
148 {
149 const char *marker_abspath;
150 svn_node_kind_t marker_kind;
151
152 marker_abspath = APR_ARRAY_IDX(markers, i, const char *);
153 SVN_ERR(svn_io_check_path(marker_abspath, &marker_kind,
154 scratch_pool));
155
156 if (marker_kind == svn_node_file)
157 {
158 svn_skel_t *work_item;
159
160 SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db,
161 local_abspath,
162 marker_abspath,
163 result_pool,
164 scratch_pool));
165
166 *work_items = svn_wc__wq_merge(*work_items, work_item,
167 result_pool);
168 }
169 }
170 }
171
172 return SVN_NO_ERROR;
173 }
174
175 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)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,
181 void *cancel_baton,
182 svn_wc_notify_func2_t notify_func,
183 void *notify_baton,
184 apr_pool_t *scratch_pool)
185 {
186 svn_wc__db_t *db = wc_ctx->db;
187 svn_error_t *err;
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;
193 int i;
194 apr_pool_t *iterpool;
195
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++)
200 {
201 svn_boolean_t conflicted = FALSE;
202 const char *repos_relpath;
203
204 svn_pool_clear(iterpool);
205
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,
210 NULL, &conflicted,
211 NULL, NULL, NULL, NULL, NULL, NULL,
212 db, local_abspath, iterpool, iterpool);
213
214 if (err)
215 {
216 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
217 {
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,
222 iterpool));
223 continue;
224 }
225 else
226 return svn_error_trace(err);
227 }
228
229 APR_ARRAY_PUSH(versioned_targets, const char *) = local_abspath;
230
231 switch (status)
232 {
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,
240 iterpool));
241
242 /* Explicitly ignore other statii */
243 default:
244 break;
245 }
246
247 if (status == svn_wc__db_status_normal
248 && kind == svn_node_dir)
249 {
250 svn_boolean_t is_wcroot;
251 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath,
252 iterpool));
253
254 if (is_wcroot)
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,
259 iterpool));
260 }
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,
266 iterpool));
267
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,
271 iterpool),
272 iterpool));
273
274 /* Prepare the on-disk delete */
275 if (!keep_local)
276 {
277 svn_skel_t *work_item;
278
279 SVN_ERR(create_delete_wq_items(&work_item, db, local_abspath, kind,
280 conflicted,
281 scratch_pool, iterpool));
282
283 work_items = svn_wc__wq_merge(work_items, work_item,
284 scratch_pool);
285 }
286 }
287
288 if (versioned_targets->nelts == 0)
289 return SVN_NO_ERROR;
290
291 SVN_ERR(svn_wc__db_op_delete_many(db, versioned_targets,
292 !keep_local /* delete_dir_externals */,
293 work_items,
294 cancel_func, cancel_baton,
295 notify_func, notify_baton,
296 iterpool));
297
298 if (work_items != NULL)
299 {
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 *);
303
304 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
305 iterpool));
306 }
307 svn_pool_destroy(iterpool);
308
309 return SVN_NO_ERROR;
310 }
311
312 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)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,
318 void *cancel_baton,
319 svn_wc_notify_func2_t notify_func,
320 void *notify_baton,
321 apr_pool_t *scratch_pool)
322 {
323 apr_pool_t *pool = scratch_pool;
324 svn_wc__db_t *db = wc_ctx->db;
325 svn_error_t *err;
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;
331
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);
337
338 if (delete_unversioned_target &&
339 err != NULL && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
340 {
341 svn_error_clear(err);
342
343 if (!keep_local)
344 SVN_ERR(erase_unversioned_from_wc(local_abspath, FALSE,
345 cancel_func, cancel_baton,
346 pool));
347 return SVN_NO_ERROR;
348 }
349 else
350 SVN_ERR(err);
351
352 switch (status)
353 {
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));
360
361 /* Explicitly ignore other statii */
362 default:
363 break;
364 }
365
366 if (status == svn_wc__db_status_normal
367 && kind == svn_node_dir)
368 {
369 svn_boolean_t is_wcroot;
370 SVN_ERR(svn_wc__db_is_wcroot(&is_wcroot, db, local_abspath, pool));
371
372 if (is_wcroot)
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));
377 }
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));
383
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),
387 pool));
388
389 /* Prepare the on-disk delete */
390 if (!keep_local)
391 {
392 SVN_ERR(create_delete_wq_items(&work_items, db, local_abspath, kind,
393 conflicted,
394 scratch_pool, scratch_pool));
395 }
396
397 SVN_ERR(svn_wc__db_op_delete(db, local_abspath,
398 NULL /*moved_to_abspath */,
399 !keep_local /* delete_dir_externals */,
400 NULL, work_items,
401 cancel_func, cancel_baton,
402 notify_func, notify_baton,
403 pool));
404
405 if (work_items)
406 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
407 scratch_pool));
408
409 return SVN_NO_ERROR;
410 }
411
412 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)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,
417 void *cancel_baton,
418 apr_pool_t *scratch_pool)
419 {
420 svn_boolean_t left_something = FALSE;
421 svn_boolean_t is_root;
422 svn_error_t *err = NULL;
423
424 SVN_ERR(svn_wc__db_is_wcroot(&is_root, db, local_abspath, scratch_pool));
425
426 SVN_ERR(svn_wc__write_check(db, is_root ? local_abspath
427 : svn_dirent_dirname(local_abspath,
428 scratch_pool),
429 scratch_pool));
430
431 SVN_ERR(svn_wc__db_op_remove_node(&left_something,
432 db, local_abspath,
433 destroy_wf /* destroy_wc */,
434 destroy_wf /* destroy_changes */,
435 NULL, NULL,
436 cancel_func, cancel_baton,
437 scratch_pool));
438
439 SVN_ERR(svn_wc__wq_run(db, local_abspath,
440 cancel_func, cancel_baton,
441 scratch_pool));
442
443 if (is_root)
444 {
445 /* Destroy the administrative area */
446 SVN_ERR(svn_wc__adm_destroy(db, local_abspath, cancel_func, cancel_baton,
447 scratch_pool));
448
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);
452 }
453
454 if (left_something || err)
455 return svn_error_create(SVN_ERR_WC_LEFT_LOCAL_MOD, err, NULL);
456
457 return SVN_NO_ERROR;
458 }
459
460 /* Implements svn_wc_status_func4_t for svn_wc_remove_from_revision_control2 */
461 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)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)
466 {
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))
472 {
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,
476 scratch_pool));
477 }
478 return SVN_NO_ERROR;
479 }
480
481 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)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,
487 void *cancel_baton,
488 apr_pool_t *scratch_pool)
489 {
490 if (instant_error)
491 {
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,
496 scratch_pool));
497 }
498 return svn_error_trace(
499 svn_wc__internal_remove_from_revision_control(wc_ctx->db,
500 local_abspath,
501 destroy_wf,
502 cancel_func,
503 cancel_baton,
504 scratch_pool));
505 }
506
507