blob: 75b32c323b8d69cad3a62354ba801187bb4a5370 [file] [log] [blame]
/*
* 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 "mergeinfo-normalizer.h"
#include "private/svn_fspath.h"
#include "private/svn_opt_private.h"
#include "private/svn_sorts_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;
/* 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, scratch_pool));
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_dirent_skip_ancestor(info->repos_root_URL,
info->URL);
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 *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;
}
/* 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;
apr_pool_t *props_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, scratch_pool));
SVN_ERR(parse_mergeinfo(result, props, result_pool, scratch_pool));
SVN_ERR(link_parents(*result, baton, scratch_pool));
svn_pool_destroy(props_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 *mergeinfo,
int idx)
{
mergeinfo_t *entry;
if (idx < 0 || mergeinfo->nelts <= idx)
{
*fs_path = "";
*parent_path = "";
*subtree_relpath = "";
*parent_mergeinfo = NULL;
*subtree_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;
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;
}
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__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;
}