diff options
Diffstat (limited to 'subversion/libsvn_client/delete.c')
-rw-r--r-- | subversion/libsvn_client/delete.c | 437 |
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; |