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