summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/delete.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/delete.c')
-rw-r--r--subversion/libsvn_client/delete.c437
1 files changed, 307 insertions, 130 deletions
diff --git a/subversion/libsvn_client/delete.c b/subversion/libsvn_client/delete.c
index 4bf5d2c..803b70c 100644
--- a/subversion/libsvn_client/delete.c
+++ b/subversion/libsvn_client/delete.c
@@ -28,6 +28,7 @@
/*** Includes. ***/
#include <apr_file_io.h>
+#include "svn_hash.h"
#include "svn_types.h"
#include "svn_pools.h"
#include "svn_wc.h"
@@ -39,65 +40,82 @@
#include "private/svn_client_private.h"
#include "private/svn_wc_private.h"
+#include "private/svn_ra_private.h"
#include "svn_private_config.h"
/*** Code. ***/
+/* Baton for find_undeletables */
+struct can_delete_baton_t
+{
+ const char *root_abspath;
+ svn_boolean_t target_missing;
+};
-/* An svn_client_status_func_t callback function for finding
+/* An svn_wc_status_func4_t callback function for finding
status structures which are not safely deletable. */
static svn_error_t *
find_undeletables(void *baton,
- const char *path,
- const svn_client_status_t *status,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
apr_pool_t *pool)
{
+ if (status->node_status == svn_wc_status_missing)
+ {
+ struct can_delete_baton_t *cdt = baton;
+
+ if (strcmp(cdt->root_abspath, local_abspath) == 0)
+ cdt->target_missing = TRUE;
+ }
+
/* Check for error-ful states. */
if (status->node_status == svn_wc_status_obstructed)
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
_("'%s' is in the way of the resource "
"actually under version control"),
- svn_dirent_local_style(path, pool));
+ svn_dirent_local_style(local_abspath, pool));
else if (! status->versioned)
return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
_("'%s' is not under version control"),
- svn_dirent_local_style(path, pool));
-
- else if ((status->node_status != svn_wc_status_normal
- && status->node_status != svn_wc_status_deleted
- && status->node_status != svn_wc_status_missing)
- ||
- (status->prop_status != svn_wc_status_none
- && status->prop_status != svn_wc_status_normal))
+ svn_dirent_local_style(local_abspath, pool));
+ else if ((status->node_status == svn_wc_status_added
+ || status->node_status == svn_wc_status_replaced)
+ && status->text_status == svn_wc_status_normal
+ && (status->prop_status == svn_wc_status_normal
+ || status->prop_status == svn_wc_status_none))
+ {
+ /* Unmodified copy. Go ahead, remove it */
+ }
+ else if (status->node_status != svn_wc_status_normal
+ && status->node_status != svn_wc_status_deleted
+ && status->node_status != svn_wc_status_missing)
return svn_error_createf(SVN_ERR_CLIENT_MODIFIED, NULL,
_("'%s' has local modifications -- commit or "
"revert them first"),
- svn_dirent_local_style(path, pool));
+ svn_dirent_local_style(local_abspath, pool));
return SVN_NO_ERROR;
}
+/* Check whether LOCAL_ABSPATH is an external and raise an error if it is.
-svn_error_t *
-svn_client__can_delete(const char *path,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
+ A file external should not be deleted since the file external is
+ implemented as a switched file and it would delete the file the
+ file external is switched to, which is not the behavior the user
+ would probably want.
+
+ A directory external should not be deleted since it is the root
+ of a different working copy. */
+static svn_error_t *
+check_external(const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
{
- svn_opt_revision_t revision;
svn_node_kind_t external_kind;
const char *defining_abspath;
- const char* local_abspath;
-
- revision.kind = svn_opt_revision_unspecified;
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool));
-
- /* A file external should not be deleted since the file external is
- implemented as a switched file and it would delete the file the
- file external is switched to, which is not the behavior the user
- would probably want. */
SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL,
NULL, NULL,
ctx->wc_ctx, local_abspath,
@@ -114,18 +132,50 @@ svn_client__can_delete(const char *path,
svn_dirent_local_style(defining_abspath,
scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Verify that the path can be deleted without losing stuff,
+ i.e. ensure that there are no modified or unversioned resources
+ under PATH. This is similar to checking the output of the status
+ command. CTX is used for the client's config options. POOL is
+ used for all temporary allocations. */
+static svn_error_t *
+can_delete_node(svn_boolean_t *target_missing,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *ignores;
+ struct can_delete_baton_t cdt;
/* Use an infinite-depth status check to see if there's anything in
or under PATH which would make it unsafe for deletion. The
status callback function find_undeletables() makes the
determination, returning an error if it finds anything that shouldn't
be deleted. */
- return svn_error_trace(svn_client_status5(NULL, ctx, path, &revision,
- svn_depth_infinity, FALSE,
- FALSE, FALSE, FALSE, FALSE,
- NULL,
- find_undeletables, NULL,
- scratch_pool));
+
+ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, scratch_pool));
+
+ cdt.root_abspath = local_abspath;
+ cdt.target_missing = FALSE;
+
+ SVN_ERR(svn_wc_walk_status(ctx->wc_ctx,
+ local_abspath,
+ svn_depth_infinity,
+ FALSE /* get_all */,
+ FALSE /* no_ignore */,
+ FALSE /* ignore_text_mod */,
+ ignores,
+ find_undeletables, &cdt,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ scratch_pool));
+
+ if (target_missing)
+ *target_missing = cdt.target_missing;
+
+ return SVN_NO_ERROR;
}
@@ -143,7 +193,7 @@ path_driver_cb_func(void **dir_baton,
static svn_error_t *
single_repos_delete(svn_ra_session_t *ra_session,
- const char *repos_root,
+ const char *base_uri,
const apr_array_header_t *relpaths,
const apr_hash_t *revprop_table,
svn_commit_callback2_t commit_callback,
@@ -171,7 +221,7 @@ single_repos_delete(svn_ra_session_t *ra_session,
const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
item = svn_client_commit_item3_create(pool);
- item->url = svn_path_url_add_component2(repos_root, relpath, pool);
+ item->url = svn_path_url_add_component2(base_uri, relpath, pool);
item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
}
@@ -187,6 +237,9 @@ single_repos_delete(svn_ra_session_t *ra_session,
log_msg, ctx, pool));
/* Fetch RA commit editor */
+ SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
+ svn_client__get_shim_callbacks(ctx->wc_ctx,
+ NULL, pool)));
SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
commit_revprops,
commit_callback,
@@ -195,9 +248,8 @@ single_repos_delete(svn_ra_session_t *ra_session,
pool));
/* Call the path-based editor driver. */
- err = svn_delta_path_driver(editor, edit_baton, SVN_INVALID_REVNUM,
- relpaths, path_driver_cb_func,
- (void *)editor, pool);
+ err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE,
+ path_driver_cb_func, (void *)editor, pool);
if (err)
{
@@ -210,6 +262,16 @@ single_repos_delete(svn_ra_session_t *ra_session,
return svn_error_trace(editor->close_edit(edit_baton, pool));
}
+
+/* Structure for tracking remote delete targets associated with a
+ specific repository. */
+struct repos_deletables_t
+{
+ svn_ra_session_t *ra_session;
+ apr_array_header_t *target_uris;
+};
+
+
static svn_error_t *
delete_urls_multi_repos(const apr_array_header_t *uris,
const apr_hash_t *revprop_table,
@@ -218,97 +280,135 @@ delete_urls_multi_repos(const apr_array_header_t *uris,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- apr_hash_t *sessions = apr_hash_make(pool);
- apr_hash_t *relpaths = apr_hash_make(pool);
+ apr_hash_t *deletables = apr_hash_make(pool);
+ apr_pool_t *iterpool;
apr_hash_index_t *hi;
int i;
- /* Create a hash of repos_root -> ra_session maps and repos_root -> relpaths
- maps, used to group the various targets. */
+ /* Create a hash mapping repository root URLs -> repos_deletables_t *
+ structures. */
for (i = 0; i < uris->nelts; i++)
{
const char *uri = APR_ARRAY_IDX(uris, i, const char *);
- svn_ra_session_t *ra_session = NULL;
- const char *repos_root = NULL;
- const char *repos_relpath = NULL;
- apr_array_header_t *relpaths_list;
+ struct repos_deletables_t *repos_deletables = NULL;
+ const char *repos_relpath;
svn_node_kind_t kind;
- for (hi = apr_hash_first(pool, sessions); hi; hi = apr_hash_next(hi))
+ for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
{
- repos_root = svn__apr_hash_index_key(hi);
- repos_relpath = svn_uri__is_child(repos_root, uri, pool);
+ const char *repos_root = svn__apr_hash_index_key(hi);
+ repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
if (repos_relpath)
{
- /* Great! We've found another uri underneath this session,
- store it and move on. */
- ra_session = svn__apr_hash_index_val(hi);
- relpaths_list = apr_hash_get(relpaths, repos_root,
- APR_HASH_KEY_STRING);
-
- APR_ARRAY_PUSH(relpaths_list, const char *) = repos_relpath;
+ /* Great! We've found another URI underneath this
+ session. We'll pick out the related RA session for
+ use later, store the new target, and move on. */
+ repos_deletables = svn__apr_hash_index_val(hi);
+ APR_ARRAY_PUSH(repos_deletables->target_uris, const char *) =
+ apr_pstrdup(pool, uri);
break;
}
}
- if (!ra_session)
+ /* If we haven't created a repos_deletable structure for this
+ delete target, we need to do. That means opening up an RA
+ session and initializing its targets list. */
+ if (!repos_deletables)
{
- /* If we haven't found a session yet, we need to open one up.
- Note that we don't have a local directory, nor a place
- to put temp files. */
- SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, uri,
- NULL, NULL, FALSE,
- TRUE, ctx, pool));
+ svn_ra_session_t *ra_session = NULL;
+ const char *repos_root;
+ apr_array_header_t *target_uris;
+
+ /* Open an RA session to (ultimately) the root of the
+ repository in which URI is found. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, uri, NULL,
+ ctx, pool, pool));
SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
-
- apr_hash_set(sessions, repos_root, APR_HASH_KEY_STRING, ra_session);
- repos_relpath = svn_uri__is_child(repos_root, uri, pool);
-
- relpaths_list = apr_array_make(pool, 1, sizeof(const char *));
- apr_hash_set(relpaths, repos_root, APR_HASH_KEY_STRING,
- relpaths_list);
- APR_ARRAY_PUSH(relpaths_list, const char *) = repos_relpath;
+ repos_relpath = svn_uri_skip_ancestor(repos_root, uri, pool);
+
+ /* Make a new relpaths list for this repository, and add
+ this URI's relpath to it. */
+ target_uris = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(target_uris, const char *) = apr_pstrdup(pool, uri);
+
+ /* Build our repos_deletables_t item and stash it in the
+ hash. */
+ repos_deletables = apr_pcalloc(pool, sizeof(*repos_deletables));
+ repos_deletables->ra_session = ra_session;
+ repos_deletables->target_uris = target_uris;
+ svn_hash_sets(deletables, repos_root, repos_deletables);
}
- /* Check we identified a non-root relpath. Return an RA error
- code for 1.6 compatibility. */
+ /* If we get here, we should have been able to calculate a
+ repos_relpath for this URI. Let's make sure. (We return an
+ RA error code otherwise for 1.6 compatibility.) */
if (!repos_relpath || !*repos_relpath)
return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
"URL '%s' not within a repository", uri);
- /* Now, test to see if the thing actually exists. */
- SVN_ERR(svn_ra_check_path(ra_session, repos_relpath, SVN_INVALID_REVNUM,
- &kind, pool));
+ /* Now, test to see if the thing actually exists in HEAD. */
+ SVN_ERR(svn_ra_check_path(repos_deletables->ra_session, repos_relpath,
+ SVN_INVALID_REVNUM, &kind, pool));
if (kind == svn_node_none)
return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
"URL '%s' does not exist", uri);
}
- /* At this point, we should have two hashs:
- SESSIONS maps repos_roots to ra_sessions.
- RELPATHS maps repos_roots to a list of decoded relpaths for that root.
-
- Now we iterate over the collection of sessions and do a commit for each
- one with the collected relpaths. */
- for (hi = apr_hash_first(pool, sessions); hi; hi = apr_hash_next(hi))
+ /* Now we iterate over the DELETABLES hash, issuing a commit for
+ each repository with its associated collected targets. */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, deletables); hi; hi = apr_hash_next(hi))
{
- const char *repos_root = svn__apr_hash_index_key(hi);
- svn_ra_session_t *ra_session = svn__apr_hash_index_val(hi);
- const apr_array_header_t *relpaths_list =
- apr_hash_get(relpaths, repos_root, APR_HASH_KEY_STRING);
+ struct repos_deletables_t *repos_deletables = svn__apr_hash_index_val(hi);
+ const char *base_uri;
+ apr_array_header_t *target_relpaths;
+
+ svn_pool_clear(iterpool);
+
+ /* We want to anchor the commit on the longest common path
+ across the targets for this one repository. If, however, one
+ of our targets is that longest common path, we need instead
+ anchor the commit on that path's immediate parent. Because
+ we're asking svn_uri_condense_targets() to remove
+ redundancies, this situation should be detectable by their
+ being returned either a) only a single, empty-path, target
+ relpath, or b) no target relpaths at all. */
+ SVN_ERR(svn_uri_condense_targets(&base_uri, &target_relpaths,
+ repos_deletables->target_uris,
+ TRUE, iterpool, iterpool));
+ SVN_ERR_ASSERT(!svn_path_is_empty(base_uri));
+ if (target_relpaths->nelts == 0)
+ {
+ const char *target_relpath;
- SVN_ERR(single_repos_delete(ra_session, repos_root, relpaths_list,
+ svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
+ APR_ARRAY_PUSH(target_relpaths, const char *) = target_relpath;
+ }
+ else if ((target_relpaths->nelts == 1)
+ && (svn_path_is_empty(APR_ARRAY_IDX(target_relpaths, 0,
+ const char *))))
+ {
+ const char *target_relpath;
+
+ svn_uri_split(&base_uri, &target_relpath, base_uri, iterpool);
+ APR_ARRAY_IDX(target_relpaths, 0, const char *) = target_relpath;
+ }
+
+ SVN_ERR(svn_ra_reparent(repos_deletables->ra_session, base_uri, pool));
+ SVN_ERR(single_repos_delete(repos_deletables->ra_session, base_uri,
+ target_relpaths,
revprop_table, commit_callback,
- commit_baton, ctx, pool));
+ commit_baton, ctx, iterpool));
}
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
svn_error_t *
-svn_client__wc_delete(const char *path,
+svn_client__wc_delete(const char *local_abspath,
svn_boolean_t force,
svn_boolean_t dry_run,
svn_boolean_t keep_local,
@@ -317,47 +417,80 @@ svn_client__wc_delete(const char *path,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- const char *local_abspath;
+ svn_boolean_t target_missing = FALSE;
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(check_external(local_abspath, ctx, pool));
if (!force && !keep_local)
/* Verify that there are no "awkward" files */
- SVN_ERR(svn_client__can_delete(local_abspath, ctx, pool));
+ SVN_ERR(can_delete_node(&target_missing, local_abspath, ctx, pool));
if (!dry_run)
/* Mark the entry for commit deletion and perform wc deletion */
return svn_error_trace(svn_wc_delete4(ctx->wc_ctx, local_abspath,
- keep_local, TRUE,
+ keep_local || target_missing
+ /*keep_local */,
+ TRUE /* delete_unversioned */,
ctx->cancel_func, ctx->cancel_baton,
notify_func, notify_baton, pool));
return SVN_NO_ERROR;
}
-/* Callback baton for delete_with_write_lock_baton. */
-struct delete_with_write_lock_baton
+svn_error_t *
+svn_client__wc_delete_many(const apr_array_header_t *targets,
+ svn_boolean_t force,
+ svn_boolean_t dry_run,
+ svn_boolean_t keep_local,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
{
- const char *path;
- svn_boolean_t force;
- svn_boolean_t keep_local;
- svn_client_ctx_t *ctx;
-};
+ int i;
+ svn_boolean_t has_non_missing = FALSE;
-/* Implements svn_wc__with_write_lock_func_t. */
-static svn_error_t *
-delete_with_write_lock_func(void *baton,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- struct delete_with_write_lock_baton *args = baton;
-
- /* Let the working copy library handle the PATH. */
- return svn_client__wc_delete(args->path, args->force,
- FALSE, args->keep_local,
- args->ctx->notify_func2,
- args->ctx->notify_baton2,
- args->ctx, scratch_pool);
+ for (i = 0; i < targets->nelts; i++)
+ {
+ const char *local_abspath = APR_ARRAY_IDX(targets, i, const char *);
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+ SVN_ERR(check_external(local_abspath, ctx, pool));
+
+ if (!force && !keep_local)
+ {
+ svn_boolean_t missing;
+ /* Verify that there are no "awkward" files */
+
+ SVN_ERR(can_delete_node(&missing, local_abspath, ctx, pool));
+
+ if (! missing)
+ has_non_missing = TRUE;
+ }
+ else
+ has_non_missing = TRUE;
+ }
+
+ if (!dry_run)
+ {
+ /* Mark the entry for commit deletion and perform wc deletion */
+
+ /* If none of the targets exists, pass keep local TRUE, to avoid
+ deleting case-different files. Detecting this in the generic case
+ from the delete code is expensive */
+ return svn_error_trace(svn_wc__delete_many(ctx->wc_ctx, targets,
+ keep_local || !has_non_missing,
+ TRUE /* delete_unversioned_target */,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ notify_func, notify_baton,
+ pool));
+ }
+
+ return SVN_NO_ERROR;
}
svn_error_t *
@@ -385,32 +518,76 @@ svn_client_delete4(const apr_array_header_t *paths,
}
else
{
- apr_pool_t *subpool = svn_pool_create(pool);
+ const char *local_abspath;
+ apr_hash_t *wcroots;
+ apr_hash_index_t *hi;
int i;
+ int j;
+ apr_pool_t *iterpool;
+ svn_boolean_t is_new_target;
+ /* Build a map of wcroots and targets within them. */
+ wcroots = apr_hash_make(pool);
+ iterpool = svn_pool_create(pool);
for (i = 0; i < paths->nelts; i++)
{
- struct delete_with_write_lock_baton dwwlb;
- const char *path = APR_ARRAY_IDX(paths, i, const char *);
- const char *local_abspath;
+ const char *wcroot_abspath;
+ apr_array_header_t *targets;
- svn_pool_clear(subpool);
+ svn_pool_clear(iterpool);
/* See if the user wants us to stop. */
if (ctx->cancel_func)
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
- SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, subpool));
- dwwlb.path = path;
- dwwlb.force = force;
- dwwlb.keep_local = keep_local;
- dwwlb.ctx = ctx;
- SVN_ERR(svn_wc__call_with_write_lock(delete_with_write_lock_func,
- &dwwlb, ctx->wc_ctx,
- local_abspath, TRUE,
- pool, subpool));
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ APR_ARRAY_IDX(paths, i,
+ const char *),
+ pool));
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ local_abspath, pool, iterpool));
+ targets = svn_hash_gets(wcroots, wcroot_abspath);
+ if (targets == NULL)
+ {
+ targets = apr_array_make(pool, 1, sizeof(const char *));
+ svn_hash_sets(wcroots, wcroot_abspath, targets);
+ }
+
+ /* Make sure targets are unique. */
+ is_new_target = TRUE;
+ for (j = 0; j < targets->nelts; j++)
+ {
+ if (strcmp(APR_ARRAY_IDX(targets, j, const char *),
+ local_abspath) == 0)
+ {
+ is_new_target = FALSE;
+ break;
+ }
+ }
+
+ if (is_new_target)
+ APR_ARRAY_PUSH(targets, const char *) = local_abspath;
+ }
+
+ /* Delete the targets from each working copy in turn. */
+ for (hi = apr_hash_first(pool, wcroots); hi; hi = apr_hash_next(hi))
+ {
+ const char *root_abspath;
+ const apr_array_header_t *targets = svn__apr_hash_index_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_dirent_condense_targets(&root_abspath, NULL, targets,
+ FALSE, iterpool, iterpool));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_client__wc_delete_many(targets, force, FALSE, keep_local,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx, iterpool),
+ ctx->wc_ctx, root_abspath, TRUE /* lock_anchor */,
+ iterpool);
}
- svn_pool_destroy(subpool);
+ svn_pool_destroy(iterpool);
}
return SVN_NO_ERROR;