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 *repos_root,
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(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;
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 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;
369 svn_pool_clear(iterpool);
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)
385 const char *target_relpath;
387 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
388 APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
390 else if ((target_relpaths->nelts == 1)
391 && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
394 const char *target_relpath;
396 svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
397 APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
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,
403 revprop_table, commit_callback,
404 commit_baton, ctx, iterpool));
406 svn_pool_destroy(iterpool);
412 svn_client__wc_delete(const char *local_abspath,
414 svn_boolean_t dry_run,
415 svn_boolean_t keep_local,
416 svn_wc_notify_func2_t notify_func,
418 svn_client_ctx_t *ctx,
421 svn_boolean_t target_missing = FALSE;
423 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
425 SVN_ERR(check_external(local_abspath, ctx, pool));
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));
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
436 TRUE /* delete_unversioned */,
437 ctx->cancel_func, ctx->cancel_baton,
438 notify_func, notify_baton, pool));
444 svn_client__wc_delete_many(const apr_array_header_t *targets,
446 svn_boolean_t dry_run,
447 svn_boolean_t keep_local,
448 svn_wc_notify_func2_t notify_func,
450 svn_client_ctx_t *ctx,
454 svn_boolean_t has_non_missing = FALSE;
456 for (i = 0; i < targets->nelts; i++)
458 const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
460 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
462 SVN_ERR(check_external(local_abspath, ctx, pool));
464 if (!force && !keep_local)
466 svn_boolean_t missing;
467 /* Verify that there are no "awkward" files */
469 SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
472 has_non_missing = TRUE;
475 has_non_missing = TRUE;
480 /* Mark the entry for commit deletion and perform wc deletion */
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 */,
490 notify_func, notify_baton,
498 svn_client_delete4(const apr_array_header_t *paths,
500 svn_boolean_t keep_local,
501 const apr_hash_t *revprop_table,
502 svn_commit_callback2_t commit_callback,
504 svn_client_ctx_t *ctx,
507 svn_boolean_t is_url;
512 SVN_ERR(svn_client__assert_homogeneous_target_type(paths));
513 is_url = svn_path_is_url(APR_ARRAY_IDX(paths, 0, const char *));
517 SVN_ERR(delete_urls_multi_repos(paths, revprop_table, commit_callback,
518 commit_baton, ctx, pool));
522 const char *local_abspath;
524 apr_hash_index_t *hi;
527 apr_pool_t *iterpool;
528 svn_boolean_t is_new_target;
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++)
535 const char *wcroot_abspath;
536 apr_array_header_t *targets;
538 svn_pool_clear(iterpool);
540 /* See if the user wants us to stop. */
541 if (ctx->cancel_func)
542 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
544 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
545 APR_ARRAY_IDX(paths, i,
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);
553 targets = apr_array_make(pool, 1, sizeof(const char *));
554 svn_hash_sets(wcroots, wcroot_abspath, targets);
557 /* Make sure targets are unique. */
558 is_new_target = TRUE;
559 for (j = 0; j < targets->nelts; j++)
561 if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
564 is_new_target = FALSE;
570 APR_ARRAY_PUSH(targets, const char *) = local_abspath;
573 /* Delete the targets from each working copy in turn. */
574 for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
576 const char *root_abspath;
577 const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
579 svn_pool_clear(iterpool);
581 SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
582 FALSE, iterpool, iterpool));
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,
588 ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
591 svn_pool_destroy(iterpool);