]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/delete.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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 *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   /* 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       struct repos_deletables_t *repos_deletables = svn__apr_hash_index_val(hi);
365       const char *base_uri;
366       apr_array_header_t *target_relpaths;
367
368       svn_pool_clear(iterpool);
369
370       /* We want to anchor the commit on the longest common path
371          across the targets for this one repository.  If, however, one
372          of our targets is that longest common path, we need instead
373          anchor the commit on that path's immediate parent.  Because
374          we're asking svn_uri_condense_targets() to remove
375          redundancies, this situation should be detectable by their
376          being returned either a) only a single, empty-path, target
377          relpath, or b) no target relpaths at all.  */
378       SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
379                                        repos_deletables->target_uris,
380                                        TRUE, iterpool, iterpool));
381       SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
382       if (target_relpaths->nelts == 0)
383         {
384           const char *target_relpath;
385
386           svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
387           APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
388         }
389       else if ((target_relpaths->nelts == 1)
390                && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
391                                                    const char *))))
392         {
393           const char *target_relpath;
394
395           svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
396           APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
397         }
398
399       SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
400       SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri,
401                                   target_relpaths,
402                                   revprop_table, commit_callback,
403                                   commit_baton, ctx, iterpool));
404     }
405   svn_pool_destroy(iterpool);
406
407   return SVN_NO_ERROR;
408 }
409
410 svn_error_t *
411 svn_client__wc_delete(const char *local_abspath,
412                       svn_boolean_t force,
413                       svn_boolean_t dry_run,
414                       svn_boolean_t keep_local,
415                       svn_wc_notify_func2_t notify_func,
416                       void *notify_baton,
417                       svn_client_ctx_t *ctx,
418                       apr_pool_t *pool)
419 {
420   svn_boolean_t target_missing = FALSE;
421
422   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
423
424   SVN_ERR(check_external(local_abspath, ctx, pool));
425
426   if (!force && !keep_local)
427     /* Verify that there are no "awkward" files */
428     SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
429
430   if (!dry_run)
431     /* Mark the entry for commit deletion and perform wc deletion */
432     return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
433                                           keep_local || target_missing
434                                                             /*keep_local */,
435                                           TRUE /* delete_unversioned */,
436                                           ctx->cancel_func, ctx->cancel_baton,
437                                           notify_func, notify_baton, pool));
438
439   return SVN_NO_ERROR;
440 }
441
442 svn_error_t *
443 svn_client__wc_delete_many(const apr_array_header_t *targets,
444                            svn_boolean_t force,
445                            svn_boolean_t dry_run,
446                            svn_boolean_t keep_local,
447                            svn_wc_notify_func2_t notify_func,
448                            void *notify_baton,
449                            svn_client_ctx_t *ctx,
450                            apr_pool_t *pool)
451 {
452   int i;
453   svn_boolean_t has_non_missing = FALSE;
454
455   for (i = 0; i < targets->nelts; i++)
456     {
457       const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
458
459       SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
460
461       SVN_ERR(check_external(local_abspath, ctx, pool));
462
463       if (!force && !keep_local)
464         {
465           svn_boolean_t missing;
466           /* Verify that there are no "awkward" files */
467
468           SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
469
470           if (! missing)
471             has_non_missing = TRUE;
472         }
473       else
474         has_non_missing = TRUE;
475     }
476
477   if (!dry_run)
478     {
479       /* Mark the entry for commit deletion and perform wc deletion */
480
481       /* If none of the targets exists, pass keep local TRUE, to avoid
482          deleting case-different files. Detecting this in the generic case
483          from the delete code is expensive */
484       return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
485                                                  keep_local || !has_non_missing,
486                                                  TRUE /* delete_unversioned_target */,
487                                                  ctx->cancel_func,
488                                                  ctx->cancel_baton,
489                                                  notify_func, notify_baton,
490                                                  pool));
491     }
492
493   return SVN_NO_ERROR;
494 }
495
496 svn_error_t *
497 svn_client_delete4(const apr_array_header_t *paths,
498                    svn_boolean_t force,
499                    svn_boolean_t keep_local,
500                    const apr_hash_t *revprop_table,
501                    svn_commit_callback2_t commit_callback,
502                    void *commit_baton,
503                    svn_client_ctx_t *ctx,
504                    apr_pool_t *pool)
505 {
506   svn_boolean_t is_url;
507
508   if (! paths->nelts)
509     return SVN_NO_ERROR;
510
511   SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
512   is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
513
514   if (is_url)
515     {
516       SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
517                                       commit_baton, ctx, pool));
518     }
519   else
520     {
521       const char *local_abspath;
522       apr_hash_t *wcroots;
523       apr_hash_index_t *hi;
524       int i;
525       int j;
526       apr_pool_t *iterpool;
527       svn_boolean_t is_new_target;
528
529       /* Build a map of wcroots and targets within them. */
530       wcroots = apr_hash_make(pool);
531       iterpool = svn_pool_create(pool);
532       for (i = 0; i < paths->nelts; i++)
533         {
534           const char *wcroot_abspath;
535           apr_array_header_t *targets;
536
537           svn_pool_clear(iterpool);
538
539           /* See if the user wants us to stop. */
540           if (ctx->cancel_func)
541             SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
542
543           SVN_ERR(svn_dirent_get_absolute(&local_abspath,
544                                           APR_ARRAY_IDX(paths, i,
545                                                         const char *),
546                                           pool));
547           SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
548                                      local_abspath, pool, iterpool));
549           targets = svn_hash_gets(wcroots, wcroot_abspath);
550           if (targets == NULL)
551             {
552               targets = apr_array_make(pool, 1, sizeof(const char *));
553               svn_hash_sets(wcroots, wcroot_abspath, targets);
554              }
555
556           /* Make sure targets are unique. */
557           is_new_target = TRUE;
558           for (j = 0; j < targets->nelts; j++)
559             {
560               if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
561                          local_abspath) == 0)
562                 {
563                   is_new_target = FALSE;
564                   break;
565                 }
566             }
567
568           if (is_new_target)
569             APR_ARRAY_PUSH(targets, const char *) = local_abspath;
570         }
571
572       /* Delete the targets from each working copy in turn. */
573       for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
574         {
575           const char *root_abspath;
576           const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
577
578           svn_pool_clear(iterpool);
579
580           SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
581                                               FALSE, iterpool, iterpool));
582
583           SVN_WC__CALL_WITH_WRITE_LOCK(
584             svn_client__wc_delete_many(targets, force, FALSE, keep_local,
585                                        ctx->notify_func2, ctx->notify_baton2,
586                                        ctx, iterpool),
587             ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
588             iterpool);
589         }
590       svn_pool_destroy(iterpool);
591     }
592
593   return SVN_NO_ERROR;
594 }