summaryrefslogtreecommitdiff
path: root/tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c')
-rw-r--r--tools/client-side/svn-mergeinfo-normalizer/wc_mergeinfo.c491
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;
+}
+