2 * delete.c: wrappers around wc delete functionality.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
30 #include <apr_file_io.h>
32 #include "svn_types.h"
33 #include "svn_pools.h"
35 #include "svn_client.h"
36 #include "svn_error.h"
37 #include "svn_dirent_uri.h"
41 #include "private/svn_client_private.h"
42 #include "private/svn_wc_private.h"
43 #include "private/svn_ra_private.h"
45 #include "svn_private_config.h"
50 /* Baton for find_undeletables */
51 struct can_delete_baton_t
53 const char *root_abspath;
54 svn_boolean_t target_missing;
57 /* An svn_wc_status_func4_t callback function for finding
58 status structures which are not safely deletable. */
60 find_undeletables(void *baton,
61 const char *local_abspath,
62 const svn_wc_status3_t *status,
65 if (status->node_status == svn_wc_status_missing)
67 struct can_delete_baton_t *cdt = baton;
69 if (strcmp(cdt->root_abspath, local_abspath) == 0)
70 cdt->target_missing = TRUE;
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))
89 /* Unmodified copy. Go ahead, remove it */
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 "
97 svn_dirent_local_style(local_abspath, pool));
102 /* Check whether LOCAL_ABSPATH is an external and raise an error if it is.
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
109 A directory external should not be deleted since it is the root
110 of a different working copy. */
112 check_external(const char *local_abspath,
113 svn_client_ctx_t *ctx,
114 apr_pool_t *scratch_pool)
116 svn_node_kind_t external_kind;
117 const char *defining_abspath;
119 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL,
121 ctx->wc_ctx, local_abspath,
123 scratch_pool, scratch_pool));
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 "
130 svn_dirent_local_style(local_abspath,
132 svn_dirent_local_style(defining_abspath,
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. */
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)
149 apr_array_header_t *ignores;
150 struct can_delete_baton_t cdt;
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
158 SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
160 cdt.root_abspath = local_abspath;
161 cdt.target_missing = FALSE;
163 SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
167 FALSE /* no_ignore */,
168 FALSE /* ignore_text_mod */,
170 find_undeletables, &cdt,
176 *target_missing = cdt.target_missing;
183 path_driver_cb_func(void **dir_baton,
185 void *callback_baton,
189 const svn_delta_editor_t *editor = callback_baton;
191 return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool);
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,
201 svn_client_ctx_t *ctx,
204 const svn_delta_editor_t *editor;
205 apr_hash_t *commit_revprops;
211 /* Create new commit items and add them to the array. */
212 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
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));
219 for (i = 0; i < relpaths->nelts; i++)
221 const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
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;
228 SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items,
236 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
237 log_msg, ctx, pool));
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,
243 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
247 NULL, TRUE, /* No lock tokens */
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);
256 return svn_error_trace(
257 svn_error_compose_create(err,
258 editor->abort_edit(edit_baton, pool)));
261 if (ctx->notify_func2)
263 svn_wc_notify_t *notify;
264 notify = svn_wc_create_notify_url(base_uri,
265 svn_wc_notify_commit_finalizing,
267 ctx->notify_func2(ctx->notify_baton2, notify, pool);
270 /* Close the edit. */
271 return svn_error_trace(editor->close_edit(edit_baton, pool));
275 /* Structure for tracking remote delete targets associated with a
276 specific repository. */
277 struct repos_deletables_t
279 svn_ra_session_t *ra_session;
280 apr_array_header_t *target_uris;
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,
289 svn_client_ctx_t *ctx,
292 apr_hash_t *deletables = apr_hash_make(pool);
293 apr_pool_t *iterpool;
294 apr_hash_index_t *hi;
297 /* Create a hash mapping repository root URLs -> repos_deletables_t *
299 for (i = 0; i < uris->nelts; i++)
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;
306 for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
308 const char *repos_root = apr_hash_this_key(hi);
310 repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
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);
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)
328 svn_ra_session_t *ra_session = NULL;
329 const char *repos_root;
330 apr_array_header_t *target_uris;
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,
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);
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);
345 /* Build our repos_deletables_t item and stash it in the
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);
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);
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);
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))
373 struct repos_deletables_t *repos_deletables = apr_hash_this_val(hi);
374 const char *base_uri;
375 apr_array_header_t *target_relpaths;
377 svn_pool_clear(iterpool);
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)
393 const char *target_relpath;
395 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
396 APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
398 else if ((target_relpaths->nelts == 1)
399 && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
402 const char *target_relpath;
404 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
405 APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
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,
411 revprop_table, commit_callback,
412 commit_baton, ctx, iterpool));
414 svn_pool_destroy(iterpool);
420 svn_client__wc_delete(const char *local_abspath,
422 svn_boolean_t dry_run,
423 svn_boolean_t keep_local,
424 svn_wc_notify_func2_t notify_func,
426 svn_client_ctx_t *ctx,
429 svn_boolean_t target_missing = FALSE;
431 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
433 SVN_ERR(check_external(local_abspath, ctx, pool));
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));
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
444 TRUE /* delete_unversioned */,
445 ctx->cancel_func, ctx->cancel_baton,
446 notify_func, notify_baton, pool));
452 svn_client__wc_delete_many(const apr_array_header_t *targets,
454 svn_boolean_t dry_run,
455 svn_boolean_t keep_local,
456 svn_wc_notify_func2_t notify_func,
458 svn_client_ctx_t *ctx,
462 svn_boolean_t has_non_missing = FALSE;
464 for (i = 0; i < targets->nelts; i++)
466 const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
468 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
470 SVN_ERR(check_external(local_abspath, ctx, pool));
472 if (!force && !keep_local)
474 svn_boolean_t missing;
475 /* Verify that there are no "awkward" files */
477 SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
480 has_non_missing = TRUE;
483 has_non_missing = TRUE;
488 /* Mark the entry for commit deletion and perform wc deletion */
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 */,
498 notify_func, notify_baton,
506 svn_client_delete4(const apr_array_header_t *paths,
508 svn_boolean_t keep_local,
509 const apr_hash_t *revprop_table,
510 svn_commit_callback2_t commit_callback,
512 svn_client_ctx_t *ctx,
515 svn_boolean_t is_url;
520 SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
521 is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
525 SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
526 commit_baton, ctx, pool));
530 const char *local_abspath;
532 apr_hash_index_t *hi;
535 apr_pool_t *iterpool;
536 svn_boolean_t is_new_target;
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++)
543 const char *wcroot_abspath;
544 apr_array_header_t *targets;
546 svn_pool_clear(iterpool);
548 /* See if the user wants us to stop. */
549 if (ctx->cancel_func)
550 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
552 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
553 APR_ARRAY_IDX(paths, i,
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);
561 targets = apr_array_make(pool, 1, sizeof(const char *));
562 svn_hash_sets(wcroots, wcroot_abspath, targets);
565 /* Make sure targets are unique. */
566 is_new_target = TRUE;
567 for (j = 0; j < targets->nelts; j++)
569 if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
572 is_new_target = FALSE;
578 APR_ARRAY_PUSH(targets, const char *) = local_abspath;
581 /* Delete the targets from each working copy in turn. */
582 for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
584 const char *root_abspath;
585 const apr_array_header_t *targets = apr_hash_this_val(hi);
587 svn_pool_clear(iterpool);
589 SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
590 FALSE, iterpool, iterpool));
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,
596 ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
599 svn_pool_destroy(iterpool);