diff options
Diffstat (limited to 'tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c')
-rw-r--r-- | tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c b/tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c new file mode 100644 index 0000000..6d7b841 --- /dev/null +++ b/tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c @@ -0,0 +1,491 @@ +/* + * wc_mergeinfo.c -- Query and store the mergeinfo. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_cmdline.h" +#include "svn_pools.h" +#include "svn_client.h" +#include "svn_string.h" +#include "svn_sorts.h" +#include "svn_dirent_uri.h" +#include "svn_props.h" +#include "svn_hash.h" + +#include "mergeinfo-normalizer.h" + +#include "private/svn_fspath.h" +#include "private/svn_opt_private.h" +#include "private/svn_sorts_private.h" +#include "private/svn_subr_private.h" +#include "svn_private_config.h" + + + +/* Our internal mergeinfo structure + * It decorates the standard svn_mergeinfo_t with path and parent info. */ +typedef struct mergeinfo_t +{ + /* The abspath of the working copy node that has this MERGINFO. */ + const char *local_path; + + /* The corresponding FS path. */ + const char *fs_path; + + /* The full URL of that node in the repository. */ + const char *url; + + /* Pointer to the closest parent mergeinfo that we found in the working + * copy. May be NULL. */ + struct mergeinfo_t *parent; + + /* All mergeinfo_t* who's PARENT points to this. May be NULL. */ + apr_array_header_t *children; + + /* The parsed mergeinfo. */ + svn_mergeinfo_t mergeinfo; +} mergeinfo_t; + +/* Parse the mergeinfo in PROPS as returned by svn_client_propget5, + * construct our internal mergeinfo representation, allocated in + * RESULT_POOL from it and return it *RESULT_P. Use SCRATCH_POOL for + * temporary allocations. */ +static svn_error_t * +parse_mergeinfo(apr_array_header_t **result_p, + apr_hash_t *props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *result = apr_array_make(result_pool, + apr_hash_count(props), + sizeof(mergeinfo_t *)); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) + { + mergeinfo_t *entry = apr_pcalloc(result_pool, sizeof(*entry)); + svn_mergeinfo_t mergeinfo; + svn_string_t *mi_string = apr_hash_this_val(hi); + + svn_pool_clear(iterpool); + SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mi_string->data, iterpool)); + + entry->local_path = apr_pstrdup(result_pool, apr_hash_this_key(hi)); + entry->mergeinfo = svn_mergeinfo_dup(mergeinfo, result_pool); + + APR_ARRAY_PUSH(result, mergeinfo_t *) = entry; + } + + svn_pool_destroy(iterpool); + *result_p = result; + + return SVN_NO_ERROR; +} + +/* Ordering function comparing two mergeinfo_t * by local abspath. */ +static int +compare_mergeinfo(const void *lhs, + const void *rhs) +{ + const mergeinfo_t *lhs_mi = *(const mergeinfo_t **)lhs; + const mergeinfo_t *rhs_mi = *(const mergeinfo_t **)rhs; + + return strcmp(lhs_mi->local_path, rhs_mi->local_path); +} + +/* Implements svn_client_info_receiver2_t. + * Updates the mergeinfo_t * given as BATON with the incoming INFO. */ +static svn_error_t * +get_urls(void *baton, + const char *target, + const svn_client_info2_t *info, + apr_pool_t *pool) +{ + mergeinfo_t *mi = baton; + apr_pool_t *target_pool = apr_hash_pool_get(mi->mergeinfo); + const char *rel_path = svn_uri_skip_ancestor(info->repos_root_URL, + info->URL, pool); + + mi->url = apr_pstrdup(target_pool, info->URL); + mi->fs_path = svn_fspath__canonicalize(rel_path, target_pool); + + return SVN_NO_ERROR; +} + +/* Sort the nodes in MERGEINFO, sub-nodes first, add working copy info to + * it and link nodes to their respective closest parents. BATON provides + * the client context. SCRATCH_POOL is used for temporaries. */ +static svn_error_t * +link_parents(apr_array_header_t *mergeinfo, + svn_min__cmd_baton_t *baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *result_pool = mergeinfo->pool; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + /* We further down assume that there are is least one entry. */ + if (mergeinfo->nelts == 0) + return SVN_NO_ERROR; + + /* sort mergeinfo by path */ + svn_sort__array(mergeinfo, compare_mergeinfo); + + /* add URL info */ + for (i = 0; i < mergeinfo->nelts; ++i) + { + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + const svn_opt_revision_t rev_working = { svn_opt_revision_working }; + + svn_pool_clear(iterpool); + SVN_ERR(svn_client_info4(entry->local_path, &rev_working, + &rev_working, svn_depth_empty, FALSE, + TRUE, FALSE, NULL, get_urls, entry, + baton->ctx, iterpool)); + } + + /* link all mergeinfo to their parent merge info - if that exists */ + for (i = 1; i < mergeinfo->nelts; ++i) + { + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + entry->parent = APR_ARRAY_IDX(mergeinfo, i - 1, mergeinfo_t *); + + while ( entry->parent + && !svn_dirent_is_ancestor(entry->parent->local_path, + entry->local_path)) + entry->parent = entry->parent->parent; + + /* Reverse pointer. */ + if (entry->parent) + { + if (!entry->parent->children) + entry->parent->children + = apr_array_make(result_pool, 4, sizeof(svn_mergeinfo_t)); + + APR_ARRAY_PUSH(entry->parent->children, svn_mergeinfo_t) + = entry->mergeinfo; + } + } + + /* break links for switched paths */ + for (i = 1; i < mergeinfo->nelts; ++i) + { + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + if (entry->parent) + { + if (!svn_uri__is_ancestor(entry->parent->url, entry->url)) + entry->parent = NULL; + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_min__read_mergeinfo(apr_array_header_t **result, + svn_min__cmd_baton_t *baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_min__opt_state_t *opt_state = baton->opt_state; + svn_client_ctx_t *ctx = baton->ctx; + + /* Pools for temporary data - to be cleaned up asap as they + * significant amounts of it. */ + apr_pool_t *props_pool = svn_pool_create(scratch_pool); + apr_pool_t *props_scratch_pool = svn_pool_create(scratch_pool); + apr_hash_t *props; + + const svn_opt_revision_t rev_working = { svn_opt_revision_working }; + + if (!baton->opt_state->quiet) + SVN_ERR(svn_cmdline_printf(scratch_pool, + _("Scanning working copy %s ...\n"), + baton->local_abspath)); + + SVN_ERR(svn_client_propget5(&props, NULL, SVN_PROP_MERGEINFO, + baton->local_abspath, &rev_working, + &rev_working, NULL, + opt_state->depth, NULL, ctx, + props_pool, props_scratch_pool)); + svn_pool_destroy(props_scratch_pool); + + SVN_ERR(parse_mergeinfo(result, props, result_pool, scratch_pool)); + svn_pool_destroy(props_pool); + + SVN_ERR(link_parents(*result, baton, scratch_pool)); + + if (!baton->opt_state->quiet) + SVN_ERR(svn_min__print_mergeinfo_stats(*result, scratch_pool)); + + return SVN_NO_ERROR; +} + +const char * +svn_min__common_parent(apr_array_header_t *mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + const char *result = NULL; + int i; + + for (i = 0; i < mergeinfo->nelts; ++i) + { + apr_hash_index_t *hi; + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + + svn_pool_clear(iterpool); + + /* Make common base path cover the wc's FS path. */ + if (result == NULL) + result = apr_pstrdup(result_pool, entry->fs_path); + else if (!svn_dirent_is_ancestor(result, entry->fs_path)) + result = svn_dirent_get_longest_ancestor(result, entry->fs_path, + result_pool); + + /* Cover the branch FS paths mentioned in the mergeinfo. */ + for (hi = apr_hash_first(scratch_pool, entry->mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char * path = apr_hash_this_key(hi); + if (!svn_dirent_is_ancestor(result, path)) + result = svn_dirent_get_longest_ancestor(result, path, + result_pool); + } + } + + svn_pool_destroy(iterpool); + return result; +} + +void +svn_min__get_mergeinfo_pair(const char **fs_path, + const char **parent_path, + const char **subtree_relpath, + svn_mergeinfo_t *parent_mergeinfo, + svn_mergeinfo_t *subtree_mergeinfo, + apr_array_header_t **siblings_mergeinfo, + apr_array_header_t *mergeinfo, + int idx) +{ + mergeinfo_t *entry; + if (idx < 0 || mergeinfo->nelts <= idx) + { + *fs_path = ""; + *parent_path = ""; + *subtree_relpath = ""; + *parent_mergeinfo = NULL; + *subtree_mergeinfo = NULL; + *siblings_mergeinfo = NULL; + + return; + } + + entry = APR_ARRAY_IDX(mergeinfo, idx, mergeinfo_t *); + *fs_path = entry->fs_path; + *subtree_mergeinfo = entry->mergeinfo; + + if (!entry->parent) + { + *parent_path = entry->local_path; + *subtree_relpath = ""; + *parent_mergeinfo = NULL; + *siblings_mergeinfo = NULL; + + return; + } + + *parent_path = entry->parent->local_path; + *subtree_relpath = svn_dirent_skip_ancestor(entry->parent->local_path, + entry->local_path); + *parent_mergeinfo = entry->parent->mergeinfo; + *siblings_mergeinfo = entry->parent->children; +} + +svn_mergeinfo_t +svn_min__get_mergeinfo(apr_array_header_t *mergeinfo, + int idx) +{ + SVN_ERR_ASSERT_NO_RETURN(idx >= 0 && idx < mergeinfo->nelts); + return APR_ARRAY_IDX(mergeinfo, idx, mergeinfo_t *)->mergeinfo; +} + +svn_error_t * +svn_min__sibling_ranges(apr_hash_t **sibling_ranges, + apr_array_header_t *sibling_mergeinfo, + const char *parent_path, + svn_rangelist_t *relevant_ranges, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + apr_hash_t *result = svn_hash__make(result_pool); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (i = 0; i < sibling_mergeinfo->nelts; ++i) + { + svn_mergeinfo_t mergeinfo; + apr_hash_index_t *hi; + + svn_pool_clear(iterpool); + mergeinfo = APR_ARRAY_IDX(sibling_mergeinfo, i, svn_mergeinfo_t); + + for (hi = apr_hash_first(iterpool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *path = apr_hash_this_key(hi); + if (svn_dirent_is_ancestor(parent_path, path)) + { + svn_rangelist_t *common, *ranges = apr_hash_this_val(hi); + SVN_ERR(svn_rangelist_intersect(&common, ranges, + relevant_ranges, TRUE, + result_pool)); + + if (common->nelts) + { + svn_hash_sets(result, apr_pstrdup(result_pool, path), + common); + } + } + } + } + + svn_pool_destroy(iterpool); + *sibling_ranges = result; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_min__write_mergeinfo(svn_min__cmd_baton_t *baton, + apr_array_header_t *mergeinfo, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = baton->ctx; + + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + int i; + + for (i = 0; i < mergeinfo->nelts; ++i) + { + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + svn_string_t *propval = NULL; + apr_array_header_t *targets; + + svn_pool_clear(iterpool); + + targets = apr_array_make(iterpool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(targets, const char *) = entry->local_path; + + /* If the mergeinfo is empty, keep the NULL PROPVAL to actually + * delete the property. */ + if (apr_hash_count(entry->mergeinfo)) + SVN_ERR(svn_mergeinfo_to_string(&propval, entry->mergeinfo, + iterpool)); + + SVN_ERR(svn_client_propset_local(SVN_PROP_MERGEINFO, propval, targets, + svn_depth_empty, FALSE, NULL, ctx, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_min__remove_empty_mergeinfo(apr_array_header_t *mergeinfo) +{ + int i; + int dest; + + for (i = 0, dest = 0; i < mergeinfo->nelts; ++i) + { + mergeinfo_t *entry = APR_ARRAY_IDX(mergeinfo, i, mergeinfo_t *); + if (apr_hash_count(entry->mergeinfo)) + { + APR_ARRAY_IDX(mergeinfo, dest, mergeinfo_t *) = entry; + ++dest; + } + } + + mergeinfo->nelts = dest; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_min__print_mergeinfo_stats(apr_array_header_t *wc_mergeinfo, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + int branch_count = 0; + int range_count = 0; + + /* Aggregate numbers. */ + int i; + for (i = 0; i < wc_mergeinfo->nelts; ++i) + { + apr_hash_index_t *hi; + svn_mergeinfo_t mergeinfo = svn_min__get_mergeinfo(wc_mergeinfo, i); + + svn_pool_clear(iterpool); + + branch_count += apr_hash_count(mergeinfo); + + for (hi = apr_hash_first(iterpool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *ranges = apr_hash_this_val(hi); + range_count += ranges->nelts; + } + } + + /* Show them. */ + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Found mergeinfo on %d nodes.\n"), + wc_mergeinfo->nelts)); + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Found %d branch entries.\n"), + branch_count)); + SVN_ERR(svn_cmdline_printf(scratch_pool, + _(" Found %d merged revision ranges.\n\n"), + range_count)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + |