]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/delete.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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   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. */
277 struct repos_deletables_t
278 {
279   svn_ra_session_t *ra_session;
280   apr_array_header_t *target_uris;
281 };
282
283
284 static svn_error_t *
285 delete_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
419 svn_error_t *
420 svn_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
451 svn_error_t *
452 svn_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
505 svn_error_t *
506 svn_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 }