delete.c revision 299742
1156230Smux/*
2156230Smux * delete.c:  wrappers around wc delete functionality.
3156230Smux *
4156230Smux * ====================================================================
5156230Smux *    Licensed to the Apache Software Foundation (ASF) under one
6156230Smux *    or more contributor license agreements.  See the NOTICE file
7156230Smux *    distributed with this work for additional information
8156230Smux *    regarding copyright ownership.  The ASF licenses this file
9156230Smux *    to you under the Apache License, Version 2.0 (the
10156230Smux *    "License"); you may not use this file except in compliance
11156230Smux *    with the License.  You may obtain a copy of the License at
12156230Smux *
13156230Smux *      http://www.apache.org/licenses/LICENSE-2.0
14156230Smux *
15156230Smux *    Unless required by applicable law or agreed to in writing,
16156230Smux *    software distributed under the License is distributed on an
17156230Smux *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18156230Smux *    KIND, either express or implied.  See the License for the
19156230Smux *    specific language governing permissions and limitations
20156230Smux *    under the License.
21156230Smux * ====================================================================
22156230Smux */
23156230Smux
24156230Smux/* ==================================================================== */
25156230Smux
26156230Smux
27156230Smux
28156230Smux/*** Includes. ***/
29156230Smux
30156230Smux#include <apr_file_io.h>
31156230Smux#include "svn_hash.h"
32156230Smux#include "svn_types.h"
33156230Smux#include "svn_pools.h"
34156230Smux#include "svn_wc.h"
35156230Smux#include "svn_client.h"
36156230Smux#include "svn_error.h"
37156230Smux#include "svn_dirent_uri.h"
38156230Smux#include "svn_path.h"
39156230Smux#include "client.h"
40156230Smux
41156230Smux#include "private/svn_client_private.h"
42156230Smux#include "private/svn_wc_private.h"
43156230Smux#include "private/svn_ra_private.h"
44156230Smux
45156230Smux#include "svn_private_config.h"
46156230Smux
47156230Smux
48156230Smux/*** Code. ***/
49156230Smux
50156230Smux/* Baton for find_undeletables */
51156230Smuxstruct can_delete_baton_t
52156230Smux{
53156230Smux  const char *root_abspath;
54156230Smux  svn_boolean_t target_missing;
55156230Smux};
56156230Smux
57156230Smux/* An svn_wc_status_func4_t callback function for finding
58156230Smux   status structures which are not safely deletable. */
59156230Smuxstatic svn_error_t *
60156230Smuxfind_undeletables(void *baton,
61156230Smux                  const char *local_abspath,
62156230Smux                  const svn_wc_status3_t *status,
63156230Smux                  apr_pool_t *pool)
64156230Smux{
65156230Smux  if (status->node_status == svn_wc_status_missing)
66156230Smux    {
67156230Smux      struct can_delete_baton_t *cdt = baton;
68156230Smux
69156230Smux      if (strcmp(cdt->root_abspath, local_abspath) == 0)
70156230Smux        cdt->target_missing = TRUE;
71156230Smux    }
72156230Smux
73156230Smux  /* Check for error-ful states. */
74156230Smux  if (status->node_status == svn_wc_status_obstructed)
75156230Smux    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
76156230Smux                             _("'%s' is in the way of the resource "
77156230Smux                               "actually under version control"),
78156230Smux                             svn_dirent_local_style(local_abspath, pool));
79156230Smux  else if (! status->versioned)
80156230Smux    return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
81156230Smux                             _("'%s' is not under version control"),
82156230Smux                             svn_dirent_local_style(local_abspath, pool));
83156230Smux  else if ((status->node_status == svn_wc_status_added
84156230Smux            || status->node_status == svn_wc_status_replaced)
85156230Smux           && status->text_status == svn_wc_status_normal
86156230Smux           && (status->prop_status == svn_wc_status_normal
87156230Smux               || status->prop_status == svn_wc_status_none))
88156230Smux    {
89156230Smux      /* Unmodified copy. Go ahead, remove it */
90156230Smux    }
91  else if (status->node_status != svn_wc_status_normal
92           && status->node_status != svn_wc_status_deleted
93           && status->node_status != svn_wc_status_missing)
94    return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL,
95                             _("'%s' has local modifications -- commit or "
96                               "revert them first"),
97                             svn_dirent_local_style(local_abspath, pool));
98
99  return SVN_NO_ERROR;
100}
101
102/* Check whether LOCAL_ABSPATH is an external and raise an error if it is.
103
104   A file external should not be deleted since the file external is
105   implemented as a switched file and it would delete the file the
106   file external is switched to, which is not the behavior the user
107   would probably want.
108
109   A directory external should not be deleted since it is the root
110   of a different working copy. */
111static svn_error_t *
112check_external(const char *local_abspath,
113               svn_client_ctx_t *ctx,
114               apr_pool_t *scratch_pool)
115{
116  svn_node_kind_t external_kind;
117  const char *defining_abspath;
118
119  SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL,
120                                     NULL, NULL,
121                                     ctx->wc_ctx, local_abspath,
122                                     local_abspath, TRUE,
123                                     scratch_pool, scratch_pool));
124
125  if (external_kind != svn_node_none)
126    return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL,
127                             _("Cannot remove the external at '%s'; "
128                               "please edit or delete the svn:externals "
129                               "property on '%s'"),
130                             svn_dirent_local_style(local_abspath,
131                                                    scratch_pool),
132                             svn_dirent_local_style(defining_abspath,
133                                                    scratch_pool));
134
135  return SVN_NO_ERROR;
136}
137
138/* Verify that the path can be deleted without losing stuff,
139   i.e. ensure that there are no modified or unversioned resources
140   under PATH.  This is similar to checking the output of the status
141   command.  CTX is used for the client's config options.  POOL is
142   used for all temporary allocations. */
143static svn_error_t *
144can_delete_node(svn_boolean_t *target_missing,
145                const char *local_abspath,
146                svn_client_ctx_t *ctx,
147                apr_pool_t *scratch_pool)
148{
149  apr_array_header_t *ignores;
150  struct can_delete_baton_t cdt;
151
152  /* Use an infinite-depth status check to see if there's anything in
153     or under PATH which would make it unsafe for deletion.  The
154     status callback function find_undeletables() makes the
155     determination, returning an error if it finds anything that shouldn't
156     be deleted. */
157
158  SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
159
160  cdt.root_abspath = local_abspath;
161  cdt.target_missing = FALSE;
162
163  SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
164                             local_abspath,
165                             svn_depth_infinity,
166                             FALSE /* get_all */,
167                             FALSE /* no_ignore */,
168                             FALSE /* ignore_text_mod */,
169                             ignores,
170                             find_undeletables, &cdt,
171                             ctx->cancel_func,
172                             ctx->cancel_baton,
173                             scratch_pool));
174
175  if (target_missing)
176    *target_missing = cdt.target_missing;
177
178  return SVN_NO_ERROR;
179}
180
181
182static svn_error_t *
183path_driver_cb_func(void **dir_baton,
184                    void *parent_baton,
185                    void *callback_baton,
186                    const char *path,
187                    apr_pool_t *pool)
188{
189  const svn_delta_editor_t *editor = callback_baton;
190  *dir_baton = NULL;
191  return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool);
192}
193
194static svn_error_t *
195single_repos_delete(svn_ra_session_t *ra_session,
196                    const char *base_uri,
197                    const apr_array_header_t *relpaths,
198                    const apr_hash_t *revprop_table,
199                    svn_commit_callback2_t commit_callback,
200                    void *commit_baton,
201                    svn_client_ctx_t *ctx,
202                    apr_pool_t *pool)
203{
204  const svn_delta_editor_t *editor;
205  apr_hash_t *commit_revprops;
206  void *edit_baton;
207  const char *log_msg;
208  int i;
209  svn_error_t *err;
210
211  /* Create new commit items and add them to the array. */
212  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
213    {
214      svn_client_commit_item3_t *item;
215      const char *tmp_file;
216      apr_array_header_t *commit_items
217        = apr_array_make(pool, relpaths->nelts, sizeof(item));
218
219      for (i = 0; i < relpaths->nelts; i++)
220        {
221          const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
222
223          item = svn_client_commit_item3_create(pool);
224          item->url = svn_path_url_add_component2(base_uri, relpath, pool);
225          item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
226          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
227        }
228      SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
229                                      ctx, pool));
230      if (! log_msg)
231        return SVN_NO_ERROR;
232    }
233  else
234    log_msg = "";
235
236  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
237                                           log_msg, ctx, pool));
238
239  /* Fetch RA commit editor */
240  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
241                        svn_client__get_shim_callbacks(ctx->wc_ctx,
242                                                       NULL, pool)));
243  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
244                                    commit_revprops,
245                                    commit_callback,
246                                    commit_baton,
247                                    NULL, TRUE, /* No lock tokens */
248                                    pool));
249
250  /* Call the path-based editor driver. */
251  err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE,
252                               path_driver_cb_func, (void *)editor, pool);
253
254  if (err)
255    {
256      return svn_error_trace(
257               svn_error_compose_create(err,
258                                        editor->abort_edit(edit_baton, pool)));
259    }
260
261  if (ctx->notify_func2)
262    {
263      svn_wc_notify_t *notify;
264      notify = svn_wc_create_notify_url(base_uri,
265                                        svn_wc_notify_commit_finalizing,
266                                        pool);
267      ctx->notify_func2(ctx->notify_baton2, notify, pool);
268    }
269
270  /* Close the edit. */
271  return svn_error_trace(editor->close_edit(edit_baton, pool));
272}
273
274
275/* Structure for tracking remote delete targets associated with a
276   specific repository. */
277struct repos_deletables_t
278{
279  svn_ra_session_t *ra_session;
280  apr_array_header_t *target_uris;
281};
282
283
284static svn_error_t *
285delete_urls_multi_repos(const apr_array_header_t *uris,
286                        const apr_hash_t *revprop_table,
287                        svn_commit_callback2_t commit_callback,
288                        void *commit_baton,
289                        svn_client_ctx_t *ctx,
290                        apr_pool_t *pool)
291{
292  apr_hash_t *deletables = apr_hash_make(pool);
293  apr_pool_t *iterpool;
294  apr_hash_index_t *hi;
295  int i;
296
297  /* Create a hash mapping repository root URLs -> repos_deletables_t *
298     structures.  */
299  for (i = 0; i < uris->nelts; i++)
300    {
301      const char *uri = APR_ARRAY_IDX(uris, i, const char *);
302      struct repos_deletables_t *repos_deletables = NULL;
303      const char *repos_relpath;
304      svn_node_kind_t kind;
305
306      for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
307        {
308          const char *repos_root = apr_hash_this_key(hi);
309
310          repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
311          if (repos_relpath)
312            {
313              /* Great!  We've found another URI underneath this
314                 session.  We'll pick out the related RA session for
315                 use later, store the new target, and move on.  */
316              repos_deletables = apr_hash_this_val(hi);
317              APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) =
318                apr_pstrdup(pool, uri);
319              break;
320            }
321        }
322
323      /* If we haven't created a repos_deletable structure for this
324         delete target, we need to do.  That means opening up an RA
325         session and initializing its targets list.  */
326      if (!repos_deletables)
327        {
328          svn_ra_session_t *ra_session = NULL;
329          const char *repos_root;
330          apr_array_header_t *target_uris;
331
332          /* Open an RA session to (ultimately) the root of the
333             repository in which URI is found.  */
334          SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL,
335                                              ctx, pool, pool));
336          SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
337          SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
338          repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
339
340          /* Make a new relpaths list for this repository, and add
341             this URI's relpath to it. */
342          target_uris = apr_array_make(pool, 1, sizeof(const char *));
343          APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri);
344
345          /* Build our repos_deletables_t item and stash it in the
346             hash. */
347          repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables));
348          repos_deletables->ra_session = ra_session;
349          repos_deletables->target_uris = target_uris;
350          svn_hash_sets(deletables, repos_root, repos_deletables);
351        }
352
353      /* If we get here, we should have been able to calculate a
354         repos_relpath for this URI.  Let's make sure.  (We return an
355         RA error code otherwise for 1.6 compatibility.)  */
356      if (!repos_relpath || !*repos_relpath)
357        return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
358                                 _("URL '%s' not within a repository"), uri);
359
360      /* Now, test to see if the thing actually exists in HEAD. */
361      SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath,
362                                SVN_INVALID_REVNUM, &kind, pool));
363      if (kind == svn_node_none)
364        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
365                                 _("URL '%s' does not exist"), uri);
366    }
367
368  /* Now we iterate over the DELETABLES hash, issuing a commit for
369     each repository with its associated collected targets. */
370  iterpool = svn_pool_create(pool);
371  for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
372    {
373      struct repos_deletables_t *repos_deletables = apr_hash_this_val(hi);
374      const char *base_uri;
375      apr_array_header_t *target_relpaths;
376
377      svn_pool_clear(iterpool);
378
379      /* We want to anchor the commit on the longest common path
380         across the targets for this one repository.  If, however, one
381         of our targets is that longest common path, we need instead
382         anchor the commit on that path's immediate parent.  Because
383         we're asking svn_uri_condense_targets() to remove
384         redundancies, this situation should be detectable by their
385         being returned either a) only a single, empty-path, target
386         relpath, or b) no target relpaths at all.  */
387      SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
388                                       repos_deletables->target_uris,
389                                       TRUE, iterpool, iterpool));
390      SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
391      if (target_relpaths->nelts == 0)
392        {
393          const char *target_relpath;
394
395          svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
396          APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
397        }
398      else if ((target_relpaths->nelts == 1)
399               && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
400                                                   const char *))))
401        {
402          const char *target_relpath;
403
404          svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
405          APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
406        }
407
408      SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
409      SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri,
410                                  target_relpaths,
411                                  revprop_table, commit_callback,
412                                  commit_baton, ctx, iterpool));
413    }
414  svn_pool_destroy(iterpool);
415
416  return SVN_NO_ERROR;
417}
418
419svn_error_t *
420svn_client__wc_delete(const char *local_abspath,
421                      svn_boolean_t force,
422                      svn_boolean_t dry_run,
423                      svn_boolean_t keep_local,
424                      svn_wc_notify_func2_t notify_func,
425                      void *notify_baton,
426                      svn_client_ctx_t *ctx,
427                      apr_pool_t *pool)
428{
429  svn_boolean_t target_missing = FALSE;
430
431  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
432
433  SVN_ERR(check_external(local_abspath, ctx, pool));
434
435  if (!force && !keep_local)
436    /* Verify that there are no "awkward" files */
437    SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
438
439  if (!dry_run)
440    /* Mark the entry for commit deletion and perform wc deletion */
441    return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
442                                          keep_local || target_missing
443                                                            /*keep_local */,
444                                          TRUE /* delete_unversioned */,
445                                          ctx->cancel_func, ctx->cancel_baton,
446                                          notify_func, notify_baton, pool));
447
448  return SVN_NO_ERROR;
449}
450
451svn_error_t *
452svn_client__wc_delete_many(const apr_array_header_t *targets,
453                           svn_boolean_t force,
454                           svn_boolean_t dry_run,
455                           svn_boolean_t keep_local,
456                           svn_wc_notify_func2_t notify_func,
457                           void *notify_baton,
458                           svn_client_ctx_t *ctx,
459                           apr_pool_t *pool)
460{
461  int i;
462  svn_boolean_t has_non_missing = FALSE;
463
464  for (i = 0; i < targets->nelts; i++)
465    {
466      const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
467
468      SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
469
470      SVN_ERR(check_external(local_abspath, ctx, pool));
471
472      if (!force && !keep_local)
473        {
474          svn_boolean_t missing;
475          /* Verify that there are no "awkward" files */
476
477          SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
478
479          if (! missing)
480            has_non_missing = TRUE;
481        }
482      else
483        has_non_missing = TRUE;
484    }
485
486  if (!dry_run)
487    {
488      /* Mark the entry for commit deletion and perform wc deletion */
489
490      /* If none of the targets exists, pass keep local TRUE, to avoid
491         deleting case-different files. Detecting this in the generic case
492         from the delete code is expensive */
493      return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
494                                                 keep_local || !has_non_missing,
495                                                 TRUE /* delete_unversioned_target */,
496                                                 ctx->cancel_func,
497                                                 ctx->cancel_baton,
498                                                 notify_func, notify_baton,
499                                                 pool));
500    }
501
502  return SVN_NO_ERROR;
503}
504
505svn_error_t *
506svn_client_delete4(const apr_array_header_t *paths,
507                   svn_boolean_t force,
508                   svn_boolean_t keep_local,
509                   const apr_hash_t *revprop_table,
510                   svn_commit_callback2_t commit_callback,
511                   void *commit_baton,
512                   svn_client_ctx_t *ctx,
513                   apr_pool_t *pool)
514{
515  svn_boolean_t is_url;
516
517  if (! paths->nelts)
518    return SVN_NO_ERROR;
519
520  SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
521  is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
522
523  if (is_url)
524    {
525      SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
526                                      commit_baton, ctx, pool));
527    }
528  else
529    {
530      const char *local_abspath;
531      apr_hash_t *wcroots;
532      apr_hash_index_t *hi;
533      int i;
534      int j;
535      apr_pool_t *iterpool;
536      svn_boolean_t is_new_target;
537
538      /* Build a map of wcroots and targets within them. */
539      wcroots = apr_hash_make(pool);
540      iterpool = svn_pool_create(pool);
541      for (i = 0; i < paths->nelts; i++)
542        {
543          const char *wcroot_abspath;
544          apr_array_header_t *targets;
545
546          svn_pool_clear(iterpool);
547
548          /* See if the user wants us to stop. */
549          if (ctx->cancel_func)
550            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
551
552          SVN_ERR(svn_dirent_get_absolute(&local_abspath,
553                                          APR_ARRAY_IDX(paths, i,
554                                                        const char *),
555                                          pool));
556          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
557                                     local_abspath, pool, iterpool));
558          targets = svn_hash_gets(wcroots, wcroot_abspath);
559          if (targets == NULL)
560            {
561              targets = apr_array_make(pool, 1, sizeof(const char *));
562              svn_hash_sets(wcroots, wcroot_abspath, targets);
563             }
564
565          /* Make sure targets are unique. */
566          is_new_target = TRUE;
567          for (j = 0; j < targets->nelts; j++)
568            {
569              if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
570                         local_abspath) == 0)
571                {
572                  is_new_target = FALSE;
573                  break;
574                }
575            }
576
577          if (is_new_target)
578            APR_ARRAY_PUSH(targets, const char *) = local_abspath;
579        }
580
581      /* Delete the targets from each working copy in turn. */
582      for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
583        {
584          const char *root_abspath;
585          const apr_array_header_t *targets = apr_hash_this_val(hi);
586
587          svn_pool_clear(iterpool);
588
589          SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
590                                              FALSE, iterpool, iterpool));
591
592          SVN_WC__CALL_WITH_WRITE_LOCK(
593            svn_client__wc_delete_many(targets, force, FALSE, keep_local,
594                                       ctx->notify_func2, ctx->notify_baton2,
595                                       ctx, iterpool),
596            ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
597            iterpool);
598        }
599      svn_pool_destroy(iterpool);
600    }
601
602  return SVN_NO_ERROR;
603}
604