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 /* Close the edit. */
262 return svn_error_trace(editor->close_edit(edit_baton, pool));
266 /* Structure for tracking remote delete targets associated with a
267 specific repository. */
268 struct repos_deletables_t
270 svn_ra_session_t *ra_session;
271 apr_array_header_t *target_uris;
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,
280 svn_client_ctx_t *ctx,
283 apr_hash_t *deletables = apr_hash_make(pool);
284 apr_pool_t *iterpool;
285 apr_hash_index_t *hi;
288 /* Create a hash mapping repository root URLs -> repos_deletables_t *
290 for (i = 0; i < uris->nelts; i++)
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;
297 for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
299 const char *repos_root = svn__apr_hash_index_key(hi);
301 repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
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);
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)
319 svn_ra_session_t *ra_session = NULL;
320 const char *repos_root;
321 apr_array_header_t *target_uris;
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,
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);
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);
336 /* Build our repos_deletables_t item and stash it in the
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);
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);
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);
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))
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;
368 svn_pool_clear(iterpool);
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)
384 const char *target_relpath;
386 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
387 APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
389 else if ((target_relpaths->nelts == 1)
390 && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
393 const char *target_relpath;
395 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
396 APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
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,
402 revprop_table, commit_callback,
403 commit_baton, ctx, iterpool));
405 svn_pool_destroy(iterpool);
411 svn_client__wc_delete(const char *local_abspath,
413 svn_boolean_t dry_run,
414 svn_boolean_t keep_local,
415 svn_wc_notify_func2_t notify_func,
417 svn_client_ctx_t *ctx,
420 svn_boolean_t target_missing = FALSE;
422 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
424 SVN_ERR(check_external(local_abspath, ctx, pool));
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));
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
435 TRUE /* delete_unversioned */,
436 ctx->cancel_func, ctx->cancel_baton,
437 notify_func, notify_baton, pool));
443 svn_client__wc_delete_many(const apr_array_header_t *targets,
445 svn_boolean_t dry_run,
446 svn_boolean_t keep_local,
447 svn_wc_notify_func2_t notify_func,
449 svn_client_ctx_t *ctx,
453 svn_boolean_t has_non_missing = FALSE;
455 for (i = 0; i < targets->nelts; i++)
457 const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
459 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
461 SVN_ERR(check_external(local_abspath, ctx, pool));
463 if (!force && !keep_local)
465 svn_boolean_t missing;
466 /* Verify that there are no "awkward" files */
468 SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
471 has_non_missing = TRUE;
474 has_non_missing = TRUE;
479 /* Mark the entry for commit deletion and perform wc deletion */
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 */,
489 notify_func, notify_baton,
497 svn_client_delete4(const apr_array_header_t *paths,
499 svn_boolean_t keep_local,
500 const apr_hash_t *revprop_table,
501 svn_commit_callback2_t commit_callback,
503 svn_client_ctx_t *ctx,
506 svn_boolean_t is_url;
511 SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
512 is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
516 SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
517 commit_baton, ctx, pool));
521 const char *local_abspath;
523 apr_hash_index_t *hi;
526 apr_pool_t *iterpool;
527 svn_boolean_t is_new_target;
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++)
534 const char *wcroot_abspath;
535 apr_array_header_t *targets;
537 svn_pool_clear(iterpool);
539 /* See if the user wants us to stop. */
540 if (ctx->cancel_func)
541 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
543 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
544 APR_ARRAY_IDX(paths, i,
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);
552 targets = apr_array_make(pool, 1, sizeof(const char *));
553 svn_hash_sets(wcroots, wcroot_abspath, targets);
556 /* Make sure targets are unique. */
557 is_new_target = TRUE;
558 for (j = 0; j < targets->nelts; j++)
560 if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
563 is_new_target = FALSE;
569 APR_ARRAY_PUSH(targets, const char *) = local_abspath;
572 /* Delete the targets from each working copy in turn. */
573 for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
575 const char *root_abspath;
576 const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
578 svn_pool_clear(iterpool);
580 SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
581 FALSE, iterpool, iterpool));
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,
587 ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
590 svn_pool_destroy(iterpool);