[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: [PATCH] 'svn patch' should remove empty dir

From: Stefan Sperling <stsp_at_elego.de>
Date: Mon, 1 Mar 2010 16:10:09 +0100

On Sat, Feb 27, 2010 at 09:09:52PM +0100, Daniel Näslund wrote:
> Index: subversion/libsvn_client/patch.c
> ===================================================================
> --- subversion/libsvn_client/patch.c (revision 916918)
> +++ subversion/libsvn_client/patch.c (arbetskopia)
> @@ -34,6 +34,7 @@
> #include "svn_path.h"
> #include "svn_pools.h"
> #include "svn_props.h"
> +#include "svn_sorts.h"
> #include "svn_subst.h"
> #include "svn_wc.h"
> #include "client.h"
> @@ -1159,6 +1160,244 @@
> return SVN_NO_ERROR;
> }
>
> +/* Baton for find_existing_children() */
> +struct status_baton
> +{
> + apr_array_header_t *existing_targets;
> + const char *parent_path;
> + apr_pool_t *result_pool;
> +};
> +
> +/* Implements svn_wc_status_func4_t. */
> +static svn_error_t *
> +find_existing_children(void *baton,
> + const char *abspath,
> + const svn_wc_status2_t *status,
> + apr_pool_t *pool)
> +{
> + struct status_baton *btn = baton;
> +
> + if (status->text_status != svn_wc_status_none
> + && status->text_status != svn_wc_status_deleted
> + && strcmp(abspath, btn->parent_path))
> + {
> + APR_ARRAY_PUSH(btn->existing_targets,
> + const char *) = apr_pstrdup(btn->result_pool,
> + abspath);
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Check if PARENT_DIR_ABSPATH has any versioned or unversioned children if
> + * we ignore the ones in TARGETS_TO_BE_DELETED. Return the answer in
> + * EMPTY. */
> +static svn_error_t *
> +is_dir_empty(svn_boolean_t *empty, const char *parent_dir_abspath,
> + svn_client_ctx_t *ctx,
> + apr_array_header_t *targets_to_be_deleted,
> + apr_pool_t *result_pool, apr_pool_t *scratch_pool)
> +{
> + struct status_baton btn;
> + svn_opt_revision_t revision;
> + int i;
> +
> + btn.existing_targets = apr_array_make(scratch_pool, 0,
> + sizeof(patch_target_t *));
> + btn.parent_path = parent_dir_abspath;
> + btn.result_pool = scratch_pool;
> + revision.kind = svn_opt_revision_unspecified;
> +
> + SVN_ERR(svn_client_status5(NULL, parent_dir_abspath, &revision,
> + find_existing_children, &btn,
> + svn_depth_immediates, TRUE, FALSE, TRUE,
> + FALSE, NULL, ctx, scratch_pool));

Not sure if svn_client_status5() is really what we want here.
Is there a public or semi-private libsvn_wc function we could
use instead? One that doesn't need a client context?

> +
> + /* Do we delete all targets? */
> + for (i = 0; i < btn.existing_targets->nelts; i++)
> + {
> + int j;
> + const char *found = APR_ARRAY_IDX(btn.existing_targets, i, const char *);
> + svn_boolean_t deleted = FALSE;
> +
> + for (j = 0; j < targets_to_be_deleted->nelts; j++)
> + {
> + patch_target_t *to_be_del;
> + to_be_del = APR_ARRAY_IDX(targets_to_be_deleted, j, patch_target_t *);
> + if (! strcmp(found, to_be_del->abs_path))
> + deleted = TRUE;

Are you missing a 'break' here?

> + }
> + if (! deleted)
> + {
> + *empty = FALSE;
> + break;
> + }
> + }
> +
> + return SVN_NO_ERROR;
> +}
> +
> +/* Add TARGET to the array of targets keyed by their parent dir in
> + * TARGETS_TO_BE_DELETED. If there is no array for the parent dir a new one
> + * is created. All allocations are done in RESULT_POOL. */
> +static svn_error_t *
> +add_target_to_hash_keyed_by_parent_dir(apr_hash_t *targets_to_be_deleted,
> + patch_target_t *target,
> + apr_pool_t *result_pool)
> +{
> + apr_array_header_t * deleted_targets_in_dir;
> +
> + /* We're using the abs_path of the target. The abs_path is not
> + * present if the path is a symlink pointing outside the wc but we
> + * know, that's not the case. */

The comma should either be removed, or moved: "... wc, but we know ...".

> + const char *dirname = svn_dirent_dirname(target->abs_path,
> + result_pool);
> +
> + deleted_targets_in_dir = apr_hash_get(targets_to_be_deleted,
> + dirname, APR_HASH_KEY_STRING);
> +
> + if (deleted_targets_in_dir)
> + {
> + APR_ARRAY_PUSH(deleted_targets_in_dir, patch_target_t *) = target;
> +
> + apr_hash_set(targets_to_be_deleted, dirname,
> + APR_HASH_KEY_STRING, deleted_targets_in_dir);
> + }
> + else
> + {
> + apr_array_header_t *new_array;
> +
> + new_array = apr_array_make(result_pool, 0, sizeof(patch_target_t *));
> + APR_ARRAY_PUSH(new_array, patch_target_t *) = target;
> + apr_hash_set(targets_to_be_deleted,
> + dirname, APR_HASH_KEY_STRING, new_array);
> + }
> + return SVN_NO_ERROR;
> +}
> +
> +/* Compare A and B and return an integer greater than, equal to, or less
> + * than 0, according to whether A has less subdirs, just as many or more
> + * subdir than B. */
> +static int
> +sort_compare_nr_of_path_elements(const svn_sort__item_t *a,
> + const svn_sort__item_t *b)

Indentation is off above.

> +{
> + const char *astr, *bstr;
> + int n_a, n_b, i;
> +
> + astr = a->key;
> + bstr = b->key;
> +
> + for (i = 0; i < a->klen; i++)
> + if (astr[i] == '/')
> + n_a++;
> +
> + for (i = 0; i < b->klen; i++)
> + if (bstr[i] == '/')
> + n_b++;
> +
> + if (n_a > n_b)
> + return -1;
> + else if (n_a == n_b)
> + return 0;
> + else
> + return 1;
> +}
> +
> +/* If all TARGETS in a dir is to be deleted, we condense those targets into
> + * one, representing their parent dirs. A new array is allocated from
> + * RESULT_POOL and returned in TARGETS. */

Maybe phrase this as:

/* Condense the list of TARGETS to be deleted such that there is
 * only a single entry single entry for each common parent directory
 * of deleted targets. Re-allocate TARGETS in RESULT_POOL.
 * Do temporary allocations in SCRATCH_POOL. */

I'm not documenting CTX cause I hope we can get rid of it, see above :)

Also, is there a way we can get around always allocating a new
target arrray?

> +static svn_error_t *
> +maybe_condense_deleted_targets(apr_array_header_t **targets,
> + svn_client_ctx_t *ctx, apr_pool_t *result_pool,
> + apr_pool_t *scratch_pool)
> +{
> + int i;
> + apr_array_header_t *new_targets;
> + apr_array_header_t *deleted_targets;
> + apr_array_header_t *sorted_keys;
> + apr_hash_t *targets_to_be_deleted;
> +
> + new_targets = apr_array_make(result_pool, 0, sizeof(patch_target_t *));
> + targets_to_be_deleted = apr_hash_make(result_pool);
> +
> + /* First we collect the targets that should be deleted ... */
> + for (i = 0; i < (*targets)->nelts; i++)
> + {
> + patch_target_t *target = APR_ARRAY_IDX(*targets, i, patch_target_t *);
> +
> + if (! target->deleted)
> + APR_ARRAY_PUSH(new_targets, patch_target_t *) = target;
> + else
> + SVN_ERR(add_target_to_hash_keyed_by_parent_dir(targets_to_be_deleted,
> + target, result_pool));
> + }
> +
> + /* ... Then we sort the keys to allow us to detect when multiple subdirs
> + * should be deleted. */
> + sorted_keys = svn_sort__hash(targets_to_be_deleted,
> + sort_compare_nr_of_path_elements,
> + scratch_pool);
> +
> + for (i = 0; i < sorted_keys->nelts ; i++)
> + {
> + svn_sort__item_t *item;
> + svn_boolean_t empty;
> + char *key;
> + apr_array_header_t *targets_array;
> + int j;
> +
> + item = &APR_ARRAY_IDX(sorted_keys, i, svn_sort__item_t);
> + key = (char *) item->key;
> + targets_array = (apr_array_header_t *) item->value;
> +
> + SVN_ERR(is_dir_empty(&empty, key, ctx, targets_array,
> + result_pool, scratch_pool));
> + if (empty)
> + {
> + patch_target_t *target = apr_palloc(result_pool, sizeof(*target));
> + target->deleted = TRUE;
> + target->kind = svn_node_dir;
> + target->abs_path = apr_pstrdup(result_pool, key);
> +
> + /* Since the dir was empty we'll use a parent_dir target instead
> + * of the child targets. Time to close unused streams! */
> + for (j = 0; j < targets_array->nelts; j++)
> + {
> + patch_target_t *child_target;
> +
> + child_target = APR_ARRAY_IDX(targets_array, j, patch_target_t *);
> +
> + if (child_target->patch)
> + SVN_ERR(svn_diff__close_patch(child_target->patch));
> + }
> +
> + SVN_ERR(add_target_to_hash_keyed_by_parent_dir(targets_to_be_deleted,
> + target, result_pool));
> +
> + /* Hack! Since the added target of the parent dir has a shorter
> + * path, we're guarenteed that it will be inserted later. We do
> + * the sort and just continue our iteration. */
> + sorted_keys = svn_sort__hash(targets_to_be_deleted,
> + sort_compare_nr_of_path_elements,
> + scratch_pool);
> + }
> + else
> + {
> + /* No empty dir, just add the targets to be deleted */
> + for (j = 0; j < targets_array->nelts; j++)
> + {
> + patch_target_t *target = APR_ARRAY_IDX(targets_array, j,
> + patch_target_t *);
> + APR_ARRAY_PUSH(new_targets, patch_target_t *) = target;
> + }
> + }
> + }
> + *targets = new_targets;
> +
> + return SVN_NO_ERROR;
> +}
> +
> /* Install a patched TARGET into the working copy at ABS_WC_PATH.
> * Use client context CTX to retrieve WC_CTX, and possibly doing
> * notifications. If DRY_RUN is TRUE, don't modify the working copy.
> @@ -1413,6 +1652,9 @@
> }
> while (patch);
>
> + SVN_ERR(maybe_condense_deleted_targets(&targets, btn->ctx, scratch_pool,
> + result_pool));

Indentation off above.

> +
> /* Install patched targets into the working copy. */
> for (i = 0; i < targets->nelts; i++)
> {
> @@ -1428,7 +1670,8 @@
> SVN_ERR(install_patched_target(target, btn->abs_wc_path,
> btn->ctx, btn->dry_run, iterpool));
> SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
> - SVN_ERR(svn_diff__close_patch(target->patch));
> + if (target->patch)
> + SVN_ERR(svn_diff__close_patch(target->patch));

Hmm... this bit looks like it could be committed separately?

> }
>
> svn_pool_destroy(iterpool);

Thanks,
Stefan
Received on 2010-03-01 16:10:59 CET

This is an archived mail posted to the Subversion Dev mailing list.