| /* |
| * 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 *const *)lhs; |
| const mergeinfo_t *rhs_mi = *(const mergeinfo_t *const *)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; |
| } |
| |