]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/delete.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_client / delete.c
1 /*
2  * delete.c:  wrappers around wc delete functionality.
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
27 \f
28 /*** Includes. ***/
29
30 #include <apr_file_io.h>
31 #include "svn_hash.h"
32 #include "svn_types.h"
33 #include "svn_pools.h"
34 #include "svn_wc.h"
35 #include "svn_client.h"
36 #include "svn_error.h"
37 #include "svn_dirent_uri.h"
38 #include "svn_path.h"
39 #include "client.h"
40
41 #include "private/svn_client_private.h"
42 #include "private/svn_wc_private.h"
43 #include "private/svn_ra_private.h"
44
45 #include "svn_private_config.h"
46
47 \f
48 /*** Code. ***/
49
50 /* Baton for find_undeletables */
51 struct can_delete_baton_t
52 {
53   const char *root_abspath;
54   svn_boolean_t target_missing;
55 };
56
57 /* An svn_wc_status_func4_t callback function for finding
58    status structures which are not safely deletable. */
59 static svn_error_t *
60 find_undeletables(void *baton,
61                   const char *local_abspath,
62                   const svn_wc_status3_t *status,
63                   apr_pool_t *pool)
64 {
65   if (status->node_status == svn_wc_status_missing)
66     {
67       struct can_delete_baton_t *cdt = baton;
68
69       if (strcmp(cdt->root_abspath, local_abspath) == 0)
70         cdt->target_missing = TRUE;
71     }
72
73   /* Check for error-ful states. */
74   if (status->node_status == svn_wc_status_obstructed)
75     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
76                              _("'%s' is in the way of the resource "
77                                "actually under version control"),
78                              svn_dirent_local_style(local_abspath, pool));
79   else if (! status->versioned)
80     return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
81                              _("'%s' is not under version control"),
82                              svn_dirent_local_style(local_abspath, pool));
83   else if ((status->node_status == svn_wc_status_added
84             || status->node_status == svn_wc_status_replaced)
85            && status->text_status == svn_wc_status_normal
86            && (status->prop_status == svn_wc_status_normal
87                || status->prop_status == svn_wc_status_none))
88     {
89       /* Unmodified copy. Go ahead, remove it */
90     }
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. */
111 static svn_error_t *
112 check_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. */
143 static svn_error_t *
144 can_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
182 static svn_error_t *
183 path_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
194 static svn_error_t *
195 single_repos_delete(svn_ra_session_t *ra_session,
196                     const char *repos_root,
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(repos_root, 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   /* Close the edit. */
262   return svn_error_trace(editor->close_edit(edit_baton, pool));
263 }
264
265
266 /* Structure for tracking remote delete targets associated with a
267    specific repository. */
268 struct repos_deletables_t
269 {
270   svn_ra_session_t *ra_session;
271   apr_array_header_t *target_uris;
272 };
273
274
275 static svn_error_t *
276 delete_urls_multi_repos(const apr_array_header_t *uris,
277                         const apr_hash_t *revprop_table,
278                         svn_commit_callback2_t commit_callback,
279                         void *commit_baton,
280                         svn_client_ctx_t *ctx,
281                         apr_pool_t *pool)
282 {
283   apr_hash_t *deletables = apr_hash_make(pool);
284   apr_pool_t *iterpool;
285   apr_hash_index_t *hi;
286   int i;
287
288   /* Create a hash mapping repository root URLs -> repos_deletables_t *
289      structures.  */
290   for (i = 0; i < uris->nelts; i++)
291     {
292       const char *uri = APR_ARRAY_IDX(uris, i, const char *);
293       struct repos_deletables_t *repos_deletables = NULL;
294       const char *repos_relpath;
295       svn_node_kind_t kind;
296
297       for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
298         {
299           const char *repos_root = svn__apr_hash_index_key(hi);
300
301           repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
302           if (repos_relpath)
303             {
304               /* Great!  We've found another URI underneath this
305                  session.  We'll pick out the related RA session for
306                  use later, store the new target, and move on.  */
307               repos_deletables = svn__apr_hash_index_val(hi);
308               APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) =
309                 apr_pstrdup(pool, uri);
310               break;
311             }
312         }
313
314       /* If we haven't created a repos_deletable structure for this
315          delete target, we need to do.  That means opening up an RA
316          session and initializing its targets list.  */
317       if (!repos_deletables)
318         {
319           svn_ra_session_t *ra_session = NULL;
320           const char *repos_root;
321           apr_array_header_t *target_uris;
322
323           /* Open an RA session to (ultimately) the root of the
324              repository in which URI is found.  */
325           SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL,
326                                               ctx, pool, pool));
327           SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
328           SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
329           repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
330
331           /* Make a new relpaths list for this repository, and add
332              this URI's relpath to it. */
333           target_uris = apr_array_make(pool, 1, sizeof(const char *));
334           APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri);
335
336           /* Build our repos_deletables_t item and stash it in the
337              hash. */
338           repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables));
339           repos_deletables->ra_session = ra_session;
340           repos_deletables->target_uris = target_uris;
341           svn_hash_sets(deletables, repos_root, repos_deletables);
342         }
343
344       /* If we get here, we should have been able to calculate a
345          repos_relpath for this URI.  Let's make sure.  (We return an
346          RA error code otherwise for 1.6 compatibility.)  */
347       if (!repos_relpath || !*repos_relpath)
348         return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
349                                  "URL '%s' not within a repository", uri);
350
351       /* Now, test to see if the thing actually exists in HEAD. */
352       SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath,
353                                 SVN_INVALID_REVNUM, &kind, pool));
354       if (kind == svn_node_none)
355         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
356                                  "URL '%s' does not exist", uri);
357     }
358
359   /* Now we iterate over the DELETABLES hash, issuing a commit for
360      each repository with its associated collected targets. */
361   iterpool = svn_pool_create(pool);
362   for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
363     {
364       const char *repos_root = svn__apr_hash_index_key(hi);
365       struct repos_deletables_t *repos_deletables = svn__apr_hash_index_val(hi);
366       const char *base_uri;
367       apr_array_header_t *target_relpaths;
368
369       svn_pool_clear(iterpool);
370
371       /* We want to anchor the commit on the longest common path
372          across the targets for this one repository.  If, however, one
373          of our targets is that longest common path, we need instead
374          anchor the commit on that path's immediate parent.  Because
375          we're asking svn_uri_condense_targets() to remove
376          redundancies, this situation should be detectable by their
377          being returned either a) only a single, empty-path, target
378          relpath, or b) no target relpaths at all.  */
379       SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
380                                        repos_deletables->target_uris,
381                                        TRUE, iterpool, iterpool));
382       SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
383       if (target_relpaths->nelts == 0)
384         {
385           const char *target_relpath;
386
387           svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
388           APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
389         }
390       else if ((target_relpaths->nelts == 1)
391                && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
392                                                    const char *))))
393         {
394           const char *target_relpath;
395
396           svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
397           APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
398         }
399
400       SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
401       SVN_ERR(single_repos_delete(repos_deletables->ra_session, repos_root,
402                                   target_relpaths,
403                                   revprop_table, commit_callback,
404                                   commit_baton, ctx, iterpool));
405     }
406   svn_pool_destroy(iterpool);
407
408   return SVN_NO_ERROR;
409 }
410
411 svn_error_t *
412 svn_client__wc_delete(const char *local_abspath,
413                       svn_boolean_t force,
414                       svn_boolean_t dry_run,
415                       svn_boolean_t keep_local,
416                       svn_wc_notify_func2_t notify_func,
417                       void *notify_baton,
418                       svn_client_ctx_t *ctx,
419                       apr_pool_t *pool)
420 {
421   svn_boolean_t target_missing = FALSE;
422
423   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
424
425   SVN_ERR(check_external(local_abspath, ctx, pool));
426
427   if (!force && !keep_local)
428     /* Verify that there are no "awkward" files */
429     SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
430
431   if (!dry_run)
432     /* Mark the entry for commit deletion and perform wc deletion */
433     return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
434                                           keep_local || target_missing
435                                                             /*keep_local */,
436                                           TRUE /* delete_unversioned */,
437                                           ctx->cancel_func, ctx->cancel_baton,
438                                           notify_func, notify_baton, pool));
439
440   return SVN_NO_ERROR;
441 }
442
443 svn_error_t *
444 svn_client__wc_delete_many(const apr_array_header_t *targets,
445                            svn_boolean_t force,
446                            svn_boolean_t dry_run,
447                            svn_boolean_t keep_local,
448                            svn_wc_notify_func2_t notify_func,
449                            void *notify_baton,
450                            svn_client_ctx_t *ctx,
451                            apr_pool_t *pool)
452 {
453   int i;
454   svn_boolean_t has_non_missing = FALSE;
455
456   for (i = 0; i < targets->nelts; i++)
457     {
458       const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
459
460       SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
461
462       SVN_ERR(check_external(local_abspath, ctx, pool));
463
464       if (!force && !keep_local)
465         {
466           svn_boolean_t missing;
467           /* Verify that there are no "awkward" files */
468
469           SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
470
471           if (! missing)
472             has_non_missing = TRUE;
473         }
474       else
475         has_non_missing = TRUE;
476     }
477
478   if (!dry_run)
479     {
480       /* Mark the entry for commit deletion and perform wc deletion */
481
482       /* If none of the targets exists, pass keep local TRUE, to avoid
483          deleting case-different files. Detecting this in the generic case
484          from the delete code is expensive */
485       return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
486                                                  keep_local || !has_non_missing,
487                                                  TRUE /* delete_unversioned_target */,
488                                                  ctx->cancel_func,
489                                                  ctx->cancel_baton,
490                                                  notify_func, notify_baton,
491                                                  pool));
492     }
493
494   return SVN_NO_ERROR;
495 }
496
497 svn_error_t *
498 svn_client_delete4(const apr_array_header_t *paths,
499                    svn_boolean_t force,
500                    svn_boolean_t keep_local,
501                    const apr_hash_t *revprop_table,
502                    svn_commit_callback2_t commit_callback,
503                    void *commit_baton,
504                    svn_client_ctx_t *ctx,
505                    apr_pool_t *pool)
506 {
507   svn_boolean_t is_url;
508
509   if (! paths->nelts)
510     return SVN_NO_ERROR;
511
512   SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
513   is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
514
515   if (is_url)
516     {
517       SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
518                                       commit_baton, ctx, pool));
519     }
520   else
521     {
522       const char *local_abspath;
523       apr_hash_t *wcroots;
524       apr_hash_index_t *hi;
525       int i;
526       int j;
527       apr_pool_t *iterpool;
528       svn_boolean_t is_new_target;
529
530       /* Build a map of wcroots and targets within them. */
531       wcroots = apr_hash_make(pool);
532       iterpool = svn_pool_create(pool);
533       for (i = 0; i < paths->nelts; i++)
534         {
535           const char *wcroot_abspath;
536           apr_array_header_t *targets;
537
538           svn_pool_clear(iterpool);
539
540           /* See if the user wants us to stop. */
541           if (ctx->cancel_func)
542             SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
543
544           SVN_ERR(svn_dirent_get_absolute(&local_abspath,
545                                           APR_ARRAY_IDX(paths, i,
546                                                         const char *),
547                                           pool));
548           SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
549                                      local_abspath, pool, iterpool));
550           targets = svn_hash_gets(wcroots, wcroot_abspath);
551           if (targets == NULL)
552             {
553               targets = apr_array_make(pool, 1, sizeof(const char *));
554               svn_hash_sets(wcroots, wcroot_abspath, targets);
555              }
556
557           /* Make sure targets are unique. */
558           is_new_target = TRUE;
559           for (j = 0; j < targets->nelts; j++)
560             {
561               if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
562                          local_abspath) == 0)
563                 {
564                   is_new_target = FALSE;
565                   break;
566                 }
567             }
568
569           if (is_new_target)
570             APR_ARRAY_PUSH(targets, const char *) = local_abspath;
571         }
572
573       /* Delete the targets from each working copy in turn. */
574       for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
575         {
576           const char *root_abspath;
577           const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
578
579           svn_pool_clear(iterpool);
580
581           SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
582                                               FALSE, iterpool, iterpool));
583
584           SVN_WC__CALL_WITH_WRITE_LOCK(
585             svn_client__wc_delete_many(targets, force, FALSE, keep_local,
586                                        ctx->notify_func2, ctx->notify_baton2,
587                                        ctx, iterpool),
588             ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
589             iterpool);
590         }
591       svn_pool_destroy(iterpool);
592     }
593
594   return SVN_NO_ERROR;
595 }