diff options
Diffstat (limited to 'subversion/libsvn_client/mergeinfo.c')
-rw-r--r-- | subversion/libsvn_client/mergeinfo.c | 2191 |
1 files changed, 2191 insertions, 0 deletions
diff --git a/subversion/libsvn_client/mergeinfo.c b/subversion/libsvn_client/mergeinfo.c new file mode 100644 index 0000000000000..453cc66a7772b --- /dev/null +++ b/subversion/libsvn_client/mergeinfo.c @@ -0,0 +1,2191 @@ +/* + * mergeinfo.c : merge history functions for the libsvn_client library + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +#include <apr_pools.h> +#include <apr_strings.h> + +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_string.h" +#include "svn_opt.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_props.h" +#include "svn_mergeinfo.h" +#include "svn_sorts.h" +#include "svn_ra.h" +#include "svn_client.h" +#include "svn_hash.h" + +#include "private/svn_opt_private.h" +#include "private/svn_mergeinfo_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_ra_private.h" +#include "private/svn_fspath.h" +#include "private/svn_client_private.h" +#include "client.h" +#include "mergeinfo.h" +#include "svn_private_config.h" + + + +svn_client__merge_path_t * +svn_client__merge_path_dup(const svn_client__merge_path_t *old, + apr_pool_t *pool) +{ + svn_client__merge_path_t *new = apr_pmemdup(pool, old, sizeof(*old)); + + new->abspath = apr_pstrdup(pool, old->abspath); + if (new->remaining_ranges) + new->remaining_ranges = svn_rangelist_dup(old->remaining_ranges, pool); + if (new->pre_merge_mergeinfo) + new->pre_merge_mergeinfo = svn_mergeinfo_dup(old->pre_merge_mergeinfo, + pool); + if (new->implicit_mergeinfo) + new->implicit_mergeinfo = svn_mergeinfo_dup(old->implicit_mergeinfo, + pool); + + return new; +} + +svn_client__merge_path_t * +svn_client__merge_path_create(const char *abspath, + apr_pool_t *pool) +{ + svn_client__merge_path_t *result = apr_pcalloc(pool, sizeof(*result)); + + result->abspath = apr_pstrdup(pool, abspath); + return result; +} + +svn_error_t * +svn_client__parse_mergeinfo(svn_mergeinfo_t *mergeinfo, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const svn_string_t *propval; + + *mergeinfo = NULL; + + /* ### Use svn_wc_prop_get() would actually be sufficient for now. + ### DannyB thinks that later we'll need behavior more like + ### svn_client__get_prop_from_wc(). */ + SVN_ERR(svn_wc_prop_get2(&propval, wc_ctx, local_abspath, SVN_PROP_MERGEINFO, + scratch_pool, scratch_pool)); + if (propval) + SVN_ERR(svn_mergeinfo_parse(mergeinfo, propval->data, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__record_wc_mergeinfo(const char *local_abspath, + svn_mergeinfo_t mergeinfo, + svn_boolean_t do_notification, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_string_t *mergeinfo_str = NULL; + svn_boolean_t mergeinfo_changes = FALSE; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + /* Convert MERGEINFO (if any) into text for storage as a property value. */ + if (mergeinfo) + SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_str, mergeinfo, scratch_pool)); + + if (do_notification && ctx->notify_func2) + SVN_ERR(svn_client__mergeinfo_status(&mergeinfo_changes, ctx->wc_ctx, + local_abspath, scratch_pool)); + + /* Record the new mergeinfo in the WC. */ + /* ### Later, we'll want behavior more analogous to + ### svn_client__get_prop_from_wc(). */ + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO, + mergeinfo_str, svn_depth_empty, + TRUE /* skip checks */, NULL, + NULL, NULL /* cancellation */, + NULL, NULL /* notification */, + scratch_pool)); + + if (do_notification && ctx->notify_func2) + { + svn_wc_notify_t *notify = + svn_wc_create_notify(local_abspath, + svn_wc_notify_merge_record_info, + scratch_pool); + if (mergeinfo_changes) + notify->prop_state = svn_wc_notify_state_merged; + else + notify->prop_state = svn_wc_notify_state_changed; + + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__record_wc_mergeinfo_catalog(apr_hash_t *result_catalog, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + if (apr_hash_count(result_catalog)) + { + int i; + apr_array_header_t *sorted_cat = + svn_sort__hash(result_catalog, svn_sort_compare_items_as_paths, + scratch_pool); + + /* Write the mergeinfo out in sorted order of the paths (presumably just + * so that the notifications are in a predictable, convenient order). */ + for (i = 0; i < sorted_cat->nelts; i++) + { + svn_sort__item_t elt = APR_ARRAY_IDX(sorted_cat, i, + svn_sort__item_t); + svn_error_t *err; + + svn_pool_clear(iterpool); + err = svn_client__record_wc_mergeinfo(elt.key, elt.value, TRUE, + ctx, iterpool); + + if (err && err->apr_err == SVN_ERR_ENTRY_NOT_FOUND) + { + /* PATH isn't just missing, it's not even versioned as far + as this working copy knows. But it was included in + MERGES, which means that the server knows about it. + Likely we don't have access to the source due to authz + restrictions. For now just clear the error and + continue... */ + svn_error_clear(err); + } + else + { + SVN_ERR(err); + } + } + } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +/*-----------------------------------------------------------------------*/ + +/*** Retrieving mergeinfo. ***/ + +svn_error_t * +svn_client__get_wc_mergeinfo(svn_mergeinfo_t *mergeinfo, + svn_boolean_t *inherited_p, + svn_mergeinfo_inheritance_t inherit, + const char *local_abspath, + const char *limit_abspath, + const char **walked_path, + svn_boolean_t ignore_invalid_mergeinfo, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *walk_relpath = ""; + svn_mergeinfo_t wc_mergeinfo; + svn_revnum_t base_revision; + apr_pool_t *iterpool; + svn_boolean_t inherited; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + if (limit_abspath) + SVN_ERR_ASSERT(svn_dirent_is_absolute(limit_abspath)); + + SVN_ERR(svn_wc__node_get_base(NULL, &base_revision, NULL, NULL, NULL, NULL, + ctx->wc_ctx, local_abspath, + TRUE /* ignore_enoent */, + FALSE /* show_hidden */, + scratch_pool, scratch_pool)); + + iterpool = svn_pool_create(scratch_pool); + while (TRUE) + { + svn_pool_clear(iterpool); + + /* Don't look for explicit mergeinfo on LOCAL_ABSPATH if we are only + interested in inherited mergeinfo. */ + if (inherit == svn_mergeinfo_nearest_ancestor) + { + wc_mergeinfo = NULL; + inherit = svn_mergeinfo_inherited; + } + else + { + /* Look for mergeinfo on LOCAL_ABSPATH. If there isn't any and we + want inherited mergeinfo, walk towards the root of the WC until + we encounter either (a) an unversioned directory, or + (b) mergeinfo. If we encounter (b), use that inherited + mergeinfo as our baseline. */ + svn_error_t *err = svn_client__parse_mergeinfo(&wc_mergeinfo, + ctx->wc_ctx, + local_abspath, + result_pool, + iterpool); + if ((ignore_invalid_mergeinfo || walk_relpath [0] != '\0') + && err + && err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + svn_error_clear(err); + wc_mergeinfo = apr_hash_make(result_pool); + break; + } + else + { + SVN_ERR(err); + } + } + + if (wc_mergeinfo == NULL && + inherit != svn_mergeinfo_explicit && + !svn_dirent_is_root(local_abspath, strlen(local_abspath))) + { + svn_boolean_t is_wc_root; + svn_boolean_t is_switched; + svn_revnum_t parent_base_rev; + svn_revnum_t parent_changed_rev; + + /* Don't look any higher than the limit path. */ + if (limit_abspath && strcmp(limit_abspath, local_abspath) == 0) + break; + + /* If we've reached the root of the working copy don't look any + higher. */ + SVN_ERR(svn_wc_check_root(&is_wc_root, &is_switched, NULL, + ctx->wc_ctx, local_abspath, iterpool)); + if (is_wc_root || is_switched) + break; + + /* No explicit mergeinfo on this path. Look higher up the + directory tree while keeping track of what we've walked. */ + walk_relpath = svn_relpath_join(svn_dirent_basename(local_abspath, + iterpool), + walk_relpath, result_pool); + local_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + + SVN_ERR(svn_wc__node_get_base(NULL, &parent_base_rev, NULL, NULL, + NULL, NULL, + ctx->wc_ctx, local_abspath, + TRUE, FALSE, + scratch_pool, scratch_pool)); + + /* ### This checks the WORKING changed_rev, so invalid on replacement + ### not even reliable in case an ancestor was copied from a + ### different location */ + SVN_ERR(svn_wc__node_get_changed_info(&parent_changed_rev, + NULL, NULL, + ctx->wc_ctx, local_abspath, + scratch_pool, + scratch_pool)); + + /* Look in LOCAL_ABSPATH's parent for inherited mergeinfo if + LOCAL_ABSPATH has no base revision because it is an uncommitted + addition, or if its base revision falls within the inclusive + range of its parent's last changed revision to the parent's base + revision; otherwise stop looking for inherited mergeinfo. */ + if (SVN_IS_VALID_REVNUM(base_revision) + && (base_revision < parent_changed_rev + || parent_base_rev < base_revision)) + break; + + /* We haven't yet risen above the root of the WC. */ + continue; + } + break; + } + + svn_pool_destroy(iterpool); + + if (svn_path_is_empty(walk_relpath)) + { + /* Mergeinfo is explicit. */ + inherited = FALSE; + *mergeinfo = wc_mergeinfo; + } + else + { + /* Mergeinfo may be inherited. */ + if (wc_mergeinfo) + { + inherited = TRUE; + SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(mergeinfo, + wc_mergeinfo, + walk_relpath, + result_pool, + scratch_pool)); + } + else + { + inherited = FALSE; + *mergeinfo = NULL; + } + } + + if (walked_path) + *walked_path = walk_relpath; + + /* Remove non-inheritable mergeinfo and paths mapped to empty ranges + which may occur if WCPATH's mergeinfo is not explicit. */ + if (inherited + && apr_hash_count(*mergeinfo)) /* Nothing to do for empty mergeinfo. */ + { + SVN_ERR(svn_mergeinfo_inheritable2(mergeinfo, *mergeinfo, NULL, + SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + TRUE, result_pool, scratch_pool)); + svn_mergeinfo__remove_empty_rangelists(*mergeinfo, result_pool); + } + + if (inherited_p) + *inherited_p = inherited; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_wc_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat, + svn_boolean_t *inherited, + svn_boolean_t include_descendants, + svn_mergeinfo_inheritance_t inherit, + const char *local_abspath, + const char *limit_path, + const char **walked_path, + svn_boolean_t ignore_invalid_mergeinfo, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *target_repos_relpath; + svn_mergeinfo_t mergeinfo; + const char *repos_root; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + *mergeinfo_cat = NULL; + SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath, + &repos_root, NULL, + ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + + /* Get the mergeinfo for the LOCAL_ABSPATH target and set *INHERITED and + *WALKED_PATH. */ + SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, inherited, inherit, + local_abspath, limit_path, + walked_path, ignore_invalid_mergeinfo, + ctx, result_pool, scratch_pool)); + + /* Add any explicit/inherited mergeinfo for LOCAL_ABSPATH to + *MERGEINFO_CAT. */ + if (mergeinfo) + { + *mergeinfo_cat = apr_hash_make(result_pool); + svn_hash_sets(*mergeinfo_cat, + apr_pstrdup(result_pool, target_repos_relpath), mergeinfo); + } + + /* If LOCAL_ABSPATH is a directory and we want the subtree mergeinfo too, + then get it. + + With WC-NG it is cheaper to do a single db transaction, than first + looking if we really have a directory. */ + if (include_descendants) + { + apr_hash_t *mergeinfo_props; + apr_hash_index_t *hi; + + SVN_ERR(svn_wc__prop_retrieve_recursive(&mergeinfo_props, + ctx->wc_ctx, local_abspath, + SVN_PROP_MERGEINFO, + scratch_pool, scratch_pool)); + + /* Convert *mergeinfo_props into a proper svn_mergeinfo_catalog_t */ + for (hi = apr_hash_first(scratch_pool, mergeinfo_props); + hi; + hi = apr_hash_next(hi)) + { + const char *node_abspath = svn__apr_hash_index_key(hi); + svn_string_t *propval = svn__apr_hash_index_val(hi); + svn_mergeinfo_t subtree_mergeinfo; + const char *repos_relpath; + + if (strcmp(node_abspath, local_abspath) == 0) + continue; /* Already parsed in svn_client__get_wc_mergeinfo */ + + SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL, + ctx->wc_ctx, node_abspath, + result_pool, scratch_pool)); + + SVN_ERR(svn_mergeinfo_parse(&subtree_mergeinfo, propval->data, + result_pool)); + + /* If the target had no explicit/inherited mergeinfo and this is the + first subtree with mergeinfo found, then the catalog will still + be NULL. */ + if (*mergeinfo_cat == NULL) + *mergeinfo_cat = apr_hash_make(result_pool); + + svn_hash_sets(*mergeinfo_cat, repos_relpath, subtree_mergeinfo); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo, + svn_ra_session_t *ra_session, + const char *url, + svn_revnum_t rev, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t squelch_incapable, + apr_pool_t *pool) +{ + svn_mergeinfo_catalog_t tgt_mergeinfo_cat; + + *target_mergeinfo = NULL; + + SVN_ERR(svn_client__get_repos_mergeinfo_catalog(&tgt_mergeinfo_cat, + ra_session, + url, rev, inherit, + squelch_incapable, FALSE, + pool, pool)); + + if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat)) + { + /* We asked only for the REL_PATH's mergeinfo, not any of its + descendants. So if there is anything in the catalog it is the + mergeinfo for REL_PATH. */ + *target_mergeinfo = + svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat)); + + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_repos_mergeinfo_catalog(svn_mergeinfo_catalog_t *mergeinfo_cat, + svn_ra_session_t *ra_session, + const char *url, + svn_revnum_t rev, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t squelch_incapable, + svn_boolean_t include_descendants, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_mergeinfo_catalog_t repos_mergeinfo_cat; + apr_array_header_t *rel_paths = apr_array_make(scratch_pool, 1, + sizeof(const char *)); + const char *old_session_url; + + APR_ARRAY_PUSH(rel_paths, const char *) = ""; + + /* Fetch the mergeinfo. */ + SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, + ra_session, url, scratch_pool)); + err = svn_ra_get_mergeinfo(ra_session, &repos_mergeinfo_cat, rel_paths, + rev, inherit, include_descendants, result_pool); + err = svn_error_compose_create( + err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)); + if (err) + { + if (squelch_incapable && err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE) + { + svn_error_clear(err); + *mergeinfo_cat = NULL; + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + + if (repos_mergeinfo_cat == NULL) + { + *mergeinfo_cat = NULL; + } + else + { + const char *session_relpath; + + SVN_ERR(svn_ra_get_path_relative_to_root(ra_session, &session_relpath, + url, scratch_pool)); + + if (session_relpath[0] == '\0') + *mergeinfo_cat = repos_mergeinfo_cat; + else + SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(mergeinfo_cat, + repos_mergeinfo_cat, + session_relpath, + result_pool, + scratch_pool)); + } + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__get_wc_or_repos_mergeinfo(svn_mergeinfo_t *target_mergeinfo, + svn_boolean_t *inherited, + svn_boolean_t *from_repos, + svn_boolean_t repos_only, + svn_mergeinfo_inheritance_t inherit, + svn_ra_session_t *ra_session, + const char *target_wcpath, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_mergeinfo_catalog_t tgt_mergeinfo_cat; + + *target_mergeinfo = NULL; + + SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog(&tgt_mergeinfo_cat, + inherited, from_repos, + FALSE, + repos_only, + FALSE, inherit, + ra_session, + target_wcpath, ctx, + pool, pool)); + if (tgt_mergeinfo_cat && apr_hash_count(tgt_mergeinfo_cat)) + { + /* We asked only for the TARGET_WCPATH's mergeinfo, not any of its + descendants. It this mergeinfo is in the catalog, it's keyed + on TARGET_WCPATH's root-relative path. We could dig that up + so we can peek into our catalog, but it ought to be the only + thing in the catalog, so we'll just fetch the first hash item. */ + *target_mergeinfo = + svn__apr_hash_index_val(apr_hash_first(pool, tgt_mergeinfo_cat)); + + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_wc_or_repos_mergeinfo_catalog( + svn_mergeinfo_catalog_t *target_mergeinfo_catalog, + svn_boolean_t *inherited_p, + svn_boolean_t *from_repos, + svn_boolean_t include_descendants, + svn_boolean_t repos_only, + svn_boolean_t ignore_invalid_mergeinfo, + svn_mergeinfo_inheritance_t inherit, + svn_ra_session_t *ra_session, + const char *target_wcpath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *url; + svn_revnum_t target_rev; + const char *local_abspath; + const char *repos_root; + const char *repos_relpath; + svn_mergeinfo_catalog_t target_mergeinfo_cat_wc = NULL; + svn_mergeinfo_catalog_t target_mergeinfo_cat_repos = NULL; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, target_wcpath, + scratch_pool)); + + if (from_repos) + *from_repos = FALSE; + + /* We may get an entry with abbreviated information from TARGET_WCPATH's + parent if TARGET_WCPATH is missing. These limited entries do not have + a URL and without that we cannot get accurate mergeinfo for + TARGET_WCPATH. */ + SVN_ERR(svn_wc__node_get_origin(NULL, &target_rev, &repos_relpath, + &repos_root, NULL, NULL, + ctx->wc_ctx, local_abspath, FALSE, + scratch_pool, scratch_pool)); + + if (repos_relpath) + url = svn_path_url_add_component2(repos_root, repos_relpath, scratch_pool); + else + url = NULL; + + if (!repos_only) + { + svn_boolean_t inherited; + SVN_ERR(svn_client__get_wc_mergeinfo_catalog(&target_mergeinfo_cat_wc, + &inherited, + include_descendants, + inherit, + local_abspath, + NULL, NULL, + ignore_invalid_mergeinfo, + ctx, + result_pool, + scratch_pool)); + if (inherited_p) + *inherited_p = inherited; + + /* If we want LOCAL_ABSPATH's inherited mergeinfo, were we able to + get it from the working copy? If not, then we must ask the + repository. */ + if (! (inherited + || (inherit == svn_mergeinfo_explicit) + || (repos_relpath + && target_mergeinfo_cat_wc + && svn_hash_gets(target_mergeinfo_cat_wc, repos_relpath)))) + { + repos_only = TRUE; + /* We already have any subtree mergeinfo from the working copy, no + need to ask the server for it again. */ + include_descendants = FALSE; + } + } + + if (repos_only) + { + /* No need to check the repos if this is a local addition. */ + if (url != NULL) + { + apr_hash_t *original_props; + + /* Check to see if we have local modifications which removed all of + TARGET_WCPATH's pristine mergeinfo. If that is the case then + TARGET_WCPATH effectively has no mergeinfo. */ + SVN_ERR(svn_wc_get_pristine_props(&original_props, + ctx->wc_ctx, local_abspath, + result_pool, scratch_pool)); + if (!svn_hash_gets(original_props, SVN_PROP_MERGEINFO)) + { + apr_pool_t *sesspool = NULL; + + if (! ra_session) + { + sesspool = svn_pool_create(scratch_pool); + SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL, + ctx, + sesspool, sesspool)); + } + + SVN_ERR(svn_client__get_repos_mergeinfo_catalog( + &target_mergeinfo_cat_repos, ra_session, + url, target_rev, inherit, + TRUE, include_descendants, + result_pool, scratch_pool)); + + if (target_mergeinfo_cat_repos + && svn_hash_gets(target_mergeinfo_cat_repos, repos_relpath)) + { + if (inherited_p) + *inherited_p = TRUE; + if (from_repos) + *from_repos = TRUE; + } + + /* If we created an RA_SESSION above, destroy it. + Otherwise, if reparented an existing session, point + it back where it was when we were called. */ + if (sesspool) + { + svn_pool_destroy(sesspool); + } + } + } + } + + /* Combine the mergeinfo from the working copy and repository as needed. */ + if (target_mergeinfo_cat_wc) + { + *target_mergeinfo_catalog = target_mergeinfo_cat_wc; + if (target_mergeinfo_cat_repos) + SVN_ERR(svn_mergeinfo_catalog_merge(*target_mergeinfo_catalog, + target_mergeinfo_cat_repos, + result_pool, scratch_pool)); + } + else if (target_mergeinfo_cat_repos) + { + *target_mergeinfo_catalog = target_mergeinfo_cat_repos; + } + else + { + *target_mergeinfo_catalog = NULL; + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__get_history_as_mergeinfo(svn_mergeinfo_t *mergeinfo_p, + svn_boolean_t *has_rev_zero_history, + const svn_client__pathrev_t *pathrev, + svn_revnum_t range_youngest, + svn_revnum_t range_oldest, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + apr_array_header_t *segments; + + /* Fetch the location segments for our URL@PEG_REVNUM. */ + if (! SVN_IS_VALID_REVNUM(range_youngest)) + range_youngest = pathrev->rev; + if (! SVN_IS_VALID_REVNUM(range_oldest)) + range_oldest = 0; + + SVN_ERR(svn_client__repos_location_segments(&segments, ra_session, + pathrev->url, pathrev->rev, + range_youngest, range_oldest, + ctx, pool)); + + if (has_rev_zero_history) + { + *has_rev_zero_history = FALSE; + if (segments->nelts) + { + svn_location_segment_t *oldest_segment = + APR_ARRAY_IDX(segments, 0, svn_location_segment_t *); + if (oldest_segment->range_start == 0) + *has_rev_zero_history = TRUE; + } + } + + SVN_ERR(svn_mergeinfo__mergeinfo_from_segments(mergeinfo_p, segments, pool)); + + return SVN_NO_ERROR; +} + + +/*-----------------------------------------------------------------------*/ + +/*** Eliding mergeinfo. ***/ + +/* Given the mergeinfo (CHILD_MERGEINFO) for a path, and the + mergeinfo of its nearest ancestor with mergeinfo (PARENT_MERGEINFO), compare + CHILD_MERGEINFO to PARENT_MERGEINFO to see if the former elides to + the latter, following the elision rules described in + svn_client__elide_mergeinfo()'s docstring. Set *ELIDES to whether + or not CHILD_MERGEINFO is redundant. + + Note: This function assumes that PARENT_MERGEINFO is definitive; + i.e. if it is NULL then the caller not only walked the entire WC + looking for inherited mergeinfo, but queried the repository if none + was found in the WC. This is rather important since this function + says empty mergeinfo should be elided if PARENT_MERGEINFO is NULL, + and we don't want to do that unless we are *certain* that the empty + mergeinfo on PATH isn't overriding anything. + + If PATH_SUFFIX and PARENT_MERGEINFO are not NULL append PATH_SUFFIX + to each path in PARENT_MERGEINFO before performing the comparison. */ +static svn_error_t * +should_elide_mergeinfo(svn_boolean_t *elides, + svn_mergeinfo_t parent_mergeinfo, + svn_mergeinfo_t child_mergeinfo, + const char *path_suffix, + apr_pool_t *scratch_pool) +{ + /* Easy out: No child mergeinfo to elide. */ + if (child_mergeinfo == NULL) + { + *elides = FALSE; + } + else if (apr_hash_count(child_mergeinfo) == 0) + { + /* Empty mergeinfo elides to empty mergeinfo or to "nothing", + i.e. it isn't overriding any parent. Otherwise it doesn't + elide. */ + *elides = (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0); + } + else if (!parent_mergeinfo || apr_hash_count(parent_mergeinfo) == 0) + { + /* Non-empty mergeinfo never elides to empty mergeinfo + or no mergeinfo. */ + *elides = FALSE; + } + else + { + /* Both CHILD_MERGEINFO and PARENT_MERGEINFO are non-NULL and + non-empty. */ + svn_mergeinfo_t path_tweaked_parent_mergeinfo; + + /* If we need to adjust the paths in PARENT_MERGEINFO do it now. */ + if (path_suffix) + SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo( + &path_tweaked_parent_mergeinfo, parent_mergeinfo, + path_suffix, scratch_pool, scratch_pool)); + else + path_tweaked_parent_mergeinfo = parent_mergeinfo; + + SVN_ERR(svn_mergeinfo__equals(elides, + path_tweaked_parent_mergeinfo, + child_mergeinfo, TRUE, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Helper for svn_client__elide_mergeinfo(). + + Given a working copy LOCAL_ABSPATH, its mergeinfo hash CHILD_MERGEINFO, and + the mergeinfo of LOCAL_ABSPATH's nearest ancestor PARENT_MERGEINFO, use + should_elide_mergeinfo() to decide whether or not CHILD_MERGEINFO elides to + PARENT_MERGEINFO; PATH_SUFFIX means the same as in that function. + + If elision does occur, then remove the mergeinfo for LOCAL_ABSPATH. + + If CHILD_MERGEINFO is NULL, do nothing. + + Use SCRATCH_POOL for temporary allocations. +*/ +static svn_error_t * +elide_mergeinfo(svn_mergeinfo_t parent_mergeinfo, + svn_mergeinfo_t child_mergeinfo, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_boolean_t elides; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); + + SVN_ERR(should_elide_mergeinfo(&elides, + parent_mergeinfo, child_mergeinfo, NULL, + scratch_pool)); + + if (elides) + { + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_MERGEINFO, + NULL, svn_depth_empty, TRUE, NULL, + NULL, NULL /* cancellation */, + NULL, NULL /* notification */, + scratch_pool)); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_merge_elide_info, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + + notify = svn_wc_create_notify(local_abspath, + svn_wc_notify_update_update, + scratch_pool); + notify->prop_state = svn_wc_notify_state_changed; + + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client__elide_mergeinfo(const char *target_abspath, + const char *wc_elision_limit_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *limit_abspath = wc_elision_limit_abspath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath)); + SVN_ERR_ASSERT(!wc_elision_limit_abspath || svn_dirent_is_absolute(wc_elision_limit_abspath)); + + /* Check for first easy out: We are already at the limit path. */ + if (!limit_abspath + || strcmp(target_abspath, limit_abspath) != 0) + { + svn_mergeinfo_t target_mergeinfo; + svn_mergeinfo_t mergeinfo = NULL; + svn_boolean_t inherited; + const char *walk_path; + svn_error_t *err; + + /* Get the TARGET_WCPATH's explicit mergeinfo. */ + err = svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited, + svn_mergeinfo_inherited, + target_abspath, + limit_abspath, + &walk_path, FALSE, + ctx, pool, pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + /* Issue #3896: If we attempt elision because invalid + mergeinfo is present on TARGET_WCPATH, then don't let + the merge fail, just skip the elision attempt. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + { + return svn_error_trace(err); + } + } + + /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to + elide, we're done. */ + if (inherited || target_mergeinfo == NULL) + return SVN_NO_ERROR; + + /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */ + err = svn_client__get_wc_mergeinfo(&mergeinfo, NULL, + svn_mergeinfo_nearest_ancestor, + target_abspath, + limit_abspath, + &walk_path, FALSE, ctx, pool, pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + /* Issue #3896 again, but invalid mergeinfo is inherited. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + { + return svn_error_trace(err); + } + } + + /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are + not limiting our search to the working copy then check if it + inherits any from the repos. */ + if (!mergeinfo && !wc_elision_limit_abspath) + { + err = svn_client__get_wc_or_repos_mergeinfo( + &mergeinfo, NULL, NULL, TRUE, + svn_mergeinfo_nearest_ancestor, + NULL, target_abspath, ctx, pool); + if (err) + { + if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) + { + /* Issue #3896 again, but invalid mergeinfo is inherited + from the repository. */ + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + { + return svn_error_trace(err); + } + } + } + + /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and + the elision is limited, then we are done.*/ + if (!mergeinfo && wc_elision_limit_abspath) + return SVN_NO_ERROR; + + SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_abspath, + ctx, pool)); + } + return SVN_NO_ERROR; +} + + +/* Set *MERGEINFO_CATALOG to the explicit or inherited mergeinfo for + PATH_OR_URL@PEG_REVISION. If INCLUDE_DESCENDANTS is true, also + store in *MERGEINFO_CATALOG the explicit mergeinfo on any subtrees + under PATH_OR_URL. Key all mergeinfo in *MERGEINFO_CATALOG on + repository relpaths. + + If no mergeinfo is found then set *MERGEINFO_CATALOG to NULL. + + Set *REPOS_ROOT to the root URL of the repository associated with + PATH_OR_URL. + + Allocate *MERGEINFO_CATALOG and all its contents in RESULT_POOL. Use + SCRATCH_POOL for all temporary allocations. + + Return SVN_ERR_UNSUPPORTED_FEATURE if the server does not support + Merge Tracking. */ +static svn_error_t * +get_mergeinfo(svn_mergeinfo_catalog_t *mergeinfo_catalog, + const char **repos_root, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + svn_boolean_t include_descendants, + svn_boolean_t ignore_invalid_mergeinfo, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_session_t *ra_session; + const char *local_abspath; + svn_boolean_t use_url = svn_path_is_url(path_or_url); + svn_client__pathrev_t *peg_loc; + + SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &peg_loc, + path_or_url, NULL, peg_revision, + peg_revision, ctx, scratch_pool)); + + /* If PATH_OR_URL is as working copy path determine if we will need to + contact the repository for the requested PEG_REVISION. */ + if (!use_url) + { + svn_client__pathrev_t *origin; + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, + scratch_pool)); + + SVN_ERR(svn_client__wc_node_get_origin(&origin, local_abspath, ctx, + scratch_pool, scratch_pool)); + if (!origin + || strcmp(origin->url, peg_loc->url) != 0 + || peg_loc->rev != origin->rev) + { + use_url = TRUE; /* Don't rely on local mergeinfo */ + } + } + + /* Check server Merge Tracking capability. */ + SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url, + scratch_pool)); + + SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool)); + + if (use_url) + { + SVN_ERR(svn_client__get_repos_mergeinfo_catalog( + mergeinfo_catalog, ra_session, peg_loc->url, peg_loc->rev, + svn_mergeinfo_inherited, FALSE, include_descendants, + result_pool, scratch_pool)); + } + else /* ! svn_path_is_url() */ + { + SVN_ERR(svn_client__get_wc_or_repos_mergeinfo_catalog( + mergeinfo_catalog, NULL, NULL, include_descendants, FALSE, + ignore_invalid_mergeinfo, svn_mergeinfo_inherited, + ra_session, path_or_url, ctx, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/*** In-memory mergeinfo elision ***/ +svn_error_t * +svn_client__elide_mergeinfo_catalog(svn_mergeinfo_catalog_t mergeinfo_catalog, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *sorted_hash; + apr_array_header_t *elidable_paths = apr_array_make(scratch_pool, 1, + sizeof(const char *)); + apr_array_header_t *dir_stack = apr_array_make(scratch_pool, 1, + sizeof(const char *)); + apr_pool_t *iterpool; + int i; + + /* Here's the general algorithm: + Walk through the paths sorted in tree order. For each path, pop + the dir_stack until it is either empty or the top item contains a parent + of the current path. Check to see if that mergeinfo is then elidable, + and build the list of elidable mergeinfo based upon that determination. + Finally, push the path of interest onto the stack, and continue. */ + sorted_hash = svn_sort__hash(mergeinfo_catalog, + svn_sort_compare_items_as_paths, + scratch_pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < sorted_hash->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_hash, i, + svn_sort__item_t); + const char *path = item->key; + + if (dir_stack->nelts > 0) + { + const char *top; + const char *path_suffix; + svn_boolean_t elides = FALSE; + + svn_pool_clear(iterpool); + + /* Pop off any paths which are not ancestors of PATH. */ + do + { + top = APR_ARRAY_IDX(dir_stack, dir_stack->nelts - 1, + const char *); + path_suffix = svn_dirent_is_child(top, path, NULL); + + if (!path_suffix) + apr_array_pop(dir_stack); + } + while (dir_stack->nelts > 0 && !path_suffix); + + /* If we have a path suffix, it means we haven't popped the stack + clean. */ + if (path_suffix) + { + SVN_ERR(should_elide_mergeinfo(&elides, + svn_hash_gets(mergeinfo_catalog, top), + svn_hash_gets(mergeinfo_catalog, path), + path_suffix, + iterpool)); + + if (elides) + APR_ARRAY_PUSH(elidable_paths, const char *) = path; + } + } + + APR_ARRAY_PUSH(dir_stack, const char *) = path; + } + svn_pool_destroy(iterpool); + + /* Now remove the elidable paths from the catalog. */ + for (i = 0; i < elidable_paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(elidable_paths, i, const char *); + svn_hash_sets(mergeinfo_catalog, path, NULL); + } + + return SVN_NO_ERROR; +} + + +/* Helper for filter_log_entry_with_rangelist(). + + DEPTH_FIRST_CATALOG_INDEX is an array of svn_sort__item_t's. The keys are + repository-absolute const char *paths, the values are svn_mergeinfo_t for + each path. + + Return a pointer to the mergeinfo value of the nearest path-wise ancestor + of FSPATH in DEPTH_FIRST_CATALOG_INDEX. A path is considered its + own ancestor, so if a key exactly matches FSPATH, return that + key's mergeinfo and set *ANCESTOR_IS_SELF to true (set it to false in all + other cases). + + If DEPTH_FIRST_CATALOG_INDEX is NULL, empty, or no ancestor is found, then + return NULL. */ +static svn_mergeinfo_t +find_nearest_ancestor(const apr_array_header_t *depth_first_catalog_index, + svn_boolean_t *ancestor_is_self, + const char *fspath) +{ + int ancestor_index = -1; + + *ancestor_is_self = FALSE; + + if (depth_first_catalog_index) + { + int i; + + for (i = 0; i < depth_first_catalog_index->nelts; i++) + { + svn_sort__item_t item = APR_ARRAY_IDX(depth_first_catalog_index, i, + svn_sort__item_t); + if (svn_fspath__skip_ancestor(item.key, fspath)) + { + ancestor_index = i; + + /* There's no nearer ancestor than FSPATH itself. */ + if (strcmp(item.key, fspath) == 0) + { + *ancestor_is_self = TRUE; + break; + } + } + + } + } + + if (ancestor_index == -1) + return NULL; + else + return (APR_ARRAY_IDX(depth_first_catalog_index, + ancestor_index, + svn_sort__item_t)).value; +} + +/* Baton for use with the filter_log_entry_with_rangelist() + svn_log_entry_receiver_t callback. */ +struct filter_log_entry_baton_t +{ + /* Is TRUE if RANGELIST describes potentially merged revisions, is FALSE + if RANGELIST describes potentially eligible revisions. */ + svn_boolean_t filtering_merged; + + /* Unsorted array of repository relative paths representing the merge + sources. There will be more than one source */ + const apr_array_header_t *merge_source_fspaths; + + /* The repository-absolute path we are calling svn_client_log5() on. */ + const char *target_fspath; + + /* Mergeinfo catalog for the tree rooted at TARGET_FSPATH. + The path keys must be repository-absolute. */ + svn_mergeinfo_catalog_t target_mergeinfo_catalog; + + /* Depth first sorted array of svn_sort__item_t's for + TARGET_MERGEINFO_CATALOG. */ + apr_array_header_t *depth_first_catalog_index; + + /* A rangelist describing all the revisions potentially merged or + potentially eligible for merging (see FILTERING_MERGED) based on + the target's explicit or inherited mergeinfo. */ + const svn_rangelist_t *rangelist; + + /* The wrapped svn_log_entry_receiver_t callback and baton which + filter_log_entry_with_rangelist() is acting as a filter for. */ + svn_log_entry_receiver_t log_receiver; + void *log_receiver_baton; + + svn_client_ctx_t *ctx; +}; + +/* Implements the svn_log_entry_receiver_t interface. BATON is a + `struct filter_log_entry_baton_t *'. + + Call the wrapped log receiver BATON->log_receiver (with + BATON->log_receiver_baton) if: + + BATON->FILTERING_MERGED is FALSE and the changes represented by LOG_ENTRY + have been fully merged from BATON->merge_source_fspaths to the WC target + based on the mergeinfo for the WC contained in BATON->TARGET_MERGEINFO_CATALOG. + + Or + + BATON->FILTERING_MERGED is TRUE and the changes represented by LOG_ENTRY + have not been merged, or only partially merged, from + BATON->merge_source_fspaths to the WC target based on the mergeinfo for the + WC contained in BATON->TARGET_MERGEINFO_CATALOG. */ +static svn_error_t * +filter_log_entry_with_rangelist(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + struct filter_log_entry_baton_t *fleb = baton; + svn_rangelist_t *intersection, *this_rangelist; + + if (fleb->ctx->cancel_func) + SVN_ERR(fleb->ctx->cancel_func(fleb->ctx->cancel_baton)); + + /* Ignore r0 because there can be no "change 0" in a merge range. */ + if (log_entry->revision == 0) + return SVN_NO_ERROR; + + this_rangelist = svn_rangelist__initialize(log_entry->revision - 1, + log_entry->revision, + TRUE, pool); + + /* Don't consider inheritance yet, see if LOG_ENTRY->REVISION is + fully or partially represented in BATON->RANGELIST. */ + SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist, + this_rangelist, FALSE, pool)); + if (! (intersection && intersection->nelts)) + return SVN_NO_ERROR; + + SVN_ERR_ASSERT(intersection->nelts == 1); + + /* Ok, we know LOG_ENTRY->REVISION is represented in BATON->RANGELIST, + but is it only partially represented, i.e. is the corresponding range in + BATON->RANGELIST non-inheritable? Ask for the same intersection as + above but consider inheritance this time, if the intersection is empty + we know the range in BATON->RANGELIST is non-inheritable. */ + SVN_ERR(svn_rangelist_intersect(&intersection, fleb->rangelist, + this_rangelist, TRUE, pool)); + log_entry->non_inheritable = !intersection->nelts; + + /* If the paths changed by LOG_ENTRY->REVISION are provided we can determine + if LOG_ENTRY->REVISION, while only partially represented in + BATON->RANGELIST, is in fact completely applied to all affected paths. + ### And ... what if it is, or if it isn't? What do we do with the answer? + And how do we cope if the changed paths are not provided? */ + if ((log_entry->non_inheritable || !fleb->filtering_merged) + && log_entry->changed_paths2) + { + apr_hash_index_t *hi; + svn_boolean_t all_subtrees_have_this_rev = TRUE; + svn_rangelist_t *this_rev_rangelist = + svn_rangelist__initialize(log_entry->revision - 1, + log_entry->revision, TRUE, pool); + apr_pool_t *iterpool = svn_pool_create(pool); + + for (hi = apr_hash_first(pool, log_entry->changed_paths2); + hi; + hi = apr_hash_next(hi)) + { + int i; + const char *path = svn__apr_hash_index_key(hi); + svn_log_changed_path2_t *change = svn__apr_hash_index_val(hi); + const char *target_fspath_affected; + svn_mergeinfo_t nearest_ancestor_mergeinfo; + svn_boolean_t found_this_revision = FALSE; + const char *merge_source_rel_target; + const char *merge_source_fspath; + svn_boolean_t ancestor_is_self; + + svn_pool_clear(iterpool); + + /* Check that PATH is a subtree of at least one of the + merge sources. If not then ignore this path. */ + for (i = 0; i < fleb->merge_source_fspaths->nelts; i++) + { + merge_source_fspath = APR_ARRAY_IDX(fleb->merge_source_fspaths, + i, const char *); + + merge_source_rel_target + = svn_fspath__skip_ancestor(merge_source_fspath, path); + if (merge_source_rel_target) + { + /* If MERGE_SOURCE was itself deleted, replaced, or added + in LOG_ENTRY->REVISION then ignore this PATH since you + can't merge a addition or deletion of yourself. */ + if (merge_source_rel_target[0] == '\0' + && (change->action != 'M')) + i = fleb->merge_source_fspaths->nelts; + break; + } + } + /* If we examined every merge source path and PATH is a child of + none of them then we can ignore this PATH. */ + if (i == fleb->merge_source_fspaths->nelts) + continue; + + /* Calculate the target path which PATH would affect if merged. */ + target_fspath_affected = svn_fspath__join(fleb->target_fspath, + merge_source_rel_target, + iterpool); + + nearest_ancestor_mergeinfo = + find_nearest_ancestor(fleb->depth_first_catalog_index, + &ancestor_is_self, + target_fspath_affected); + + /* Issue #3791: A path should never have explicit mergeinfo + describing its own addition (that's self-referential). Nor will + it have explicit mergeinfo describing its own deletion (we + obviously can't add new mergeinfo to a path we are deleting). + + This lack of explicit mergeinfo should not cause such revisions + to show up as eligible however. If PATH was deleted, replaced, + or added in LOG_ENTRY->REVISION, but the corresponding + TARGET_PATH_AFFECTED already exists and has explicit mergeinfo + describing merges from PATH *after* LOG_ENTRY->REVISION, then + ignore this PATH. If it was deleted in LOG_ENTRY->REVISION it's + obviously back. If it was added or replaced it's still around + possibly it was replaced one or more times, but it's back now. + Regardless, LOG_ENTRY->REVISION is *not* an eligible revision! */ + if (ancestor_is_self /* Explicit mergeinfo on TARGET_PATH_AFFECTED */ + && (change->action != 'M')) + { + svn_rangelist_t *rangelist = + svn_hash_gets(nearest_ancestor_mergeinfo, path); + svn_merge_range_t *youngest_range = APR_ARRAY_IDX( + rangelist, rangelist->nelts - 1, svn_merge_range_t *); + + if (youngest_range + && (youngest_range->end > log_entry->revision)) + continue; + } + + if (nearest_ancestor_mergeinfo) + { + apr_hash_index_t *hi2; + + for (hi2 = apr_hash_first(iterpool, nearest_ancestor_mergeinfo); + hi2; + hi2 = apr_hash_next(hi2)) + { + const char *mergeinfo_path = svn__apr_hash_index_key(hi2); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi2); + + /* Does the mergeinfo for PATH reflect if + LOG_ENTRY->REVISION was previously merged + from MERGE_SOURCE_FSPATH? */ + if (svn_fspath__skip_ancestor(merge_source_fspath, + mergeinfo_path)) + { + /* Something was merged from MERGE_SOURCE_FSPATH, does + it include LOG_ENTRY->REVISION? */ + SVN_ERR(svn_rangelist_intersect(&intersection, + rangelist, + this_rev_rangelist, + FALSE, + iterpool)); + if (intersection->nelts) + { + if (ancestor_is_self) + { + /* TARGET_PATH_AFFECTED has explicit mergeinfo, + so we don't need to worry if that mergeinfo + is inheritable or not. */ + found_this_revision = TRUE; + break; + } + else + { + /* TARGET_PATH_AFFECTED inherited its mergeinfo, + so we have to ignore non-inheritable + ranges. */ + SVN_ERR(svn_rangelist_intersect( + &intersection, + rangelist, + this_rev_rangelist, + TRUE, iterpool)); + if (intersection->nelts) + { + found_this_revision = TRUE; + break; + } + } + } + } + } + } + + if (!found_this_revision) + { + /* As soon as any PATH is found that is not fully merged for + LOG_ENTRY->REVISION then we can stop. */ + all_subtrees_have_this_rev = FALSE; + break; + } + } + + svn_pool_destroy(iterpool); + + if (all_subtrees_have_this_rev) + { + if (fleb->filtering_merged) + log_entry->non_inheritable = FALSE; + else + return SVN_NO_ERROR; + } + } + + /* Call the wrapped log receiver which this function is filtering for. */ + return fleb->log_receiver(fleb->log_receiver_baton, log_entry, pool); +} + +static svn_error_t * +logs_for_mergeinfo_rangelist(const char *source_url, + const apr_array_header_t *merge_source_fspaths, + svn_boolean_t filtering_merged, + const svn_rangelist_t *rangelist, + svn_boolean_t oldest_revs_first, + svn_mergeinfo_catalog_t target_mergeinfo_catalog, + const char *target_fspath, + svn_boolean_t discover_changed_paths, + const apr_array_header_t *revprops, + svn_log_entry_receiver_t log_receiver, + void *log_receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *target; + svn_merge_range_t *oldest_range, *youngest_range; + apr_array_header_t *revision_ranges; + svn_opt_revision_t oldest_rev, youngest_rev; + struct filter_log_entry_baton_t fleb; + + if (! rangelist->nelts) + return SVN_NO_ERROR; + + /* Sort the rangelist. */ + qsort(rangelist->elts, rangelist->nelts, + rangelist->elt_size, svn_sort_compare_ranges); + + /* Build a single-member log target list using SOURCE_URL. */ + target = apr_array_make(scratch_pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(target, const char *) = source_url; + + /* Calculate and construct the bounds of our log request. */ + youngest_range = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1, + svn_merge_range_t *); + youngest_rev.kind = svn_opt_revision_number; + youngest_rev.value.number = youngest_range->end; + oldest_range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); + oldest_rev.kind = svn_opt_revision_number; + oldest_rev.value.number = oldest_range->start; + + if (! target_mergeinfo_catalog) + target_mergeinfo_catalog = apr_hash_make(scratch_pool); + + /* FILTER_LOG_ENTRY_BATON_T->TARGET_MERGEINFO_CATALOG's keys are required + to be repository-absolute. */ + SVN_ERR(svn_mergeinfo__add_prefix_to_catalog(&target_mergeinfo_catalog, + target_mergeinfo_catalog, "/", + scratch_pool, scratch_pool)); + + /* Build the log filtering callback baton. */ + fleb.filtering_merged = filtering_merged; + fleb.merge_source_fspaths = merge_source_fspaths; + fleb.target_mergeinfo_catalog = target_mergeinfo_catalog; + fleb.depth_first_catalog_index = + svn_sort__hash(target_mergeinfo_catalog, + svn_sort_compare_items_as_paths, + scratch_pool); + fleb.target_fspath = target_fspath; + fleb.rangelist = rangelist; + fleb.log_receiver = log_receiver; + fleb.log_receiver_baton = log_receiver_baton; + fleb.ctx = ctx; + + /* Drive the log. */ + revision_ranges = apr_array_make(scratch_pool, 1, + sizeof(svn_opt_revision_range_t *)); + if (oldest_revs_first) + APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *) + = svn_opt__revision_range_create(&oldest_rev, &youngest_rev, scratch_pool); + else + APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *) + = svn_opt__revision_range_create(&youngest_rev, &oldest_rev, scratch_pool); + SVN_ERR(svn_client_log5(target, &youngest_rev, revision_ranges, + 0, discover_changed_paths, FALSE, FALSE, revprops, + filter_log_entry_with_rangelist, &fleb, ctx, + scratch_pool)); + + /* Check for cancellation. */ + if (ctx->cancel_func) + SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); + + return SVN_NO_ERROR; +} + +/* Set *OUT_MERGEINFO to a shallow copy of MERGEINFO with each source path + converted to a (URI-encoded) URL based on REPOS_ROOT_URL. *OUT_MERGEINFO + is declared as 'apr_hash_t *' because its key do not obey the rules of + 'svn_mergeinfo_t'. + + Allocate *OUT_MERGEINFO and the new keys in RESULT_POOL. Use + SCRATCH_POOL for any temporary allocations. */ +static svn_error_t * +mergeinfo_relpaths_to_urls(apr_hash_t **out_mergeinfo, + svn_mergeinfo_t mergeinfo, + const char *repos_root_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *out_mergeinfo = NULL; + if (mergeinfo) + { + apr_hash_index_t *hi; + apr_hash_t *full_path_mergeinfo = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; hi = apr_hash_next(hi)) + { + const char *key = svn__apr_hash_index_key(hi); + void *val = svn__apr_hash_index_val(hi); + + svn_hash_sets(full_path_mergeinfo, + svn_path_url_add_component2(repos_root_url, key + 1, + result_pool), + val); + } + *out_mergeinfo = full_path_mergeinfo; + } + + return SVN_NO_ERROR; +} + + +/*** Public APIs ***/ + +svn_error_t * +svn_client_mergeinfo_get_merged(apr_hash_t **mergeinfo_p, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *repos_root; + svn_mergeinfo_catalog_t mergeinfo_cat; + svn_mergeinfo_t mergeinfo; + + SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url, + peg_revision, FALSE, FALSE, ctx, pool, pool)); + if (mergeinfo_cat) + { + const char *repos_relpath; + + if (! svn_path_is_url(path_or_url)) + { + SVN_ERR(svn_dirent_get_absolute(&path_or_url, path_or_url, pool)); + SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, NULL, NULL, + ctx->wc_ctx, path_or_url, + pool, pool)); + } + else + { + repos_relpath = svn_uri_skip_ancestor(repos_root, path_or_url, pool); + + SVN_ERR_ASSERT(repos_relpath != NULL); /* Or get_mergeinfo failed */ + } + + mergeinfo = svn_hash_gets(mergeinfo_cat, repos_relpath); + } + else + { + mergeinfo = NULL; + } + + SVN_ERR(mergeinfo_relpaths_to_urls(mergeinfo_p, mergeinfo, + repos_root, pool, pool)); + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_client_mergeinfo_log2(svn_boolean_t finding_merged, + const char *target_path_or_url, + const svn_opt_revision_t *target_peg_revision, + const char *source_path_or_url, + const svn_opt_revision_t *source_peg_revision, + const svn_opt_revision_t *source_start_revision, + const svn_opt_revision_t *source_end_revision, + svn_log_entry_receiver_t log_receiver, + void *log_receiver_baton, + svn_boolean_t discover_changed_paths, + svn_depth_t depth, + const apr_array_header_t *revprops, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *log_target = NULL; + const char *repos_root; + const char *target_repos_relpath; + svn_mergeinfo_catalog_t target_mergeinfo_cat; + + /* A hash of paths, at or under TARGET_PATH_OR_URL, mapped to + rangelists. Not technically mergeinfo, so not using the + svn_mergeinfo_t type. */ + apr_hash_t *inheritable_subtree_merges; + + svn_mergeinfo_t source_history; + svn_mergeinfo_t target_history; + svn_rangelist_t *master_noninheritable_rangelist; + svn_rangelist_t *master_inheritable_rangelist; + apr_array_header_t *merge_source_fspaths = + apr_array_make(scratch_pool, 1, sizeof(const char *)); + apr_hash_index_t *hi_catalog; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + svn_boolean_t oldest_revs_first = TRUE; + + /* We currently only support depth = empty | infinity. */ + if (depth != svn_depth_infinity && depth != svn_depth_empty) + return svn_error_create( + SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Only depths 'infinity' and 'empty' are currently supported")); + + /* Validate and sanitize the incoming source operative revision range. */ + if (!((source_start_revision->kind == svn_opt_revision_unspecified) || + (source_start_revision->kind == svn_opt_revision_number) || + (source_start_revision->kind == svn_opt_revision_date) || + (source_start_revision->kind == svn_opt_revision_head))) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + if (!((source_end_revision->kind == svn_opt_revision_unspecified) || + (source_end_revision->kind == svn_opt_revision_number) || + (source_end_revision->kind == svn_opt_revision_date) || + (source_end_revision->kind == svn_opt_revision_head))) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + if ((source_end_revision->kind != svn_opt_revision_unspecified) + && (source_start_revision->kind == svn_opt_revision_unspecified)) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + if ((source_end_revision->kind == svn_opt_revision_unspecified) + && (source_start_revision->kind != svn_opt_revision_unspecified)) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); + + /* We need the union of TARGET_PATH_OR_URL@TARGET_PEG_REVISION's mergeinfo + and MERGE_SOURCE_URL's history. It's not enough to do path + matching, because renames in the history of MERGE_SOURCE_URL + throw that all in a tizzy. Of course, if there's no mergeinfo on + the target, that vastly simplifies matters (we'll have nothing to + do). */ + /* This get_mergeinfo() call doubles as a mergeinfo capabilities check. */ + SVN_ERR(get_mergeinfo(&target_mergeinfo_cat, &repos_root, + target_path_or_url, target_peg_revision, + depth == svn_depth_infinity, TRUE, + ctx, scratch_pool, scratch_pool)); + + if (!svn_path_is_url(target_path_or_url)) + { + SVN_ERR(svn_dirent_get_absolute(&target_path_or_url, + target_path_or_url, scratch_pool)); + SVN_ERR(svn_wc__node_get_repos_info(NULL, &target_repos_relpath, + NULL, NULL, + ctx->wc_ctx, target_path_or_url, + scratch_pool, scratch_pool)); + } + else + { + target_repos_relpath = svn_uri_skip_ancestor(repos_root, + target_path_or_url, + scratch_pool); + + /* TARGET_REPOS_REL should be non-NULL, else get_mergeinfo + should have failed. */ + SVN_ERR_ASSERT(target_repos_relpath != NULL); + } + + if (!target_mergeinfo_cat) + { + /* If we are looking for what has been merged and there is no + mergeinfo then we already know the answer. If we are looking + for eligible revisions then create a catalog with empty mergeinfo + on the target. This is semantically equivalent to no mergeinfo + and gives us something to combine with MERGE_SOURCE_URL's + history. */ + if (finding_merged) + { + return SVN_NO_ERROR; + } + else + { + target_mergeinfo_cat = apr_hash_make(scratch_pool); + svn_hash_sets(target_mergeinfo_cat, target_repos_relpath, + apr_hash_make(scratch_pool)); + } + } + + /* Fetch the location history as mergeinfo, for the source branch + * (between the given start and end revisions), and, if we're finding + * merged revisions, then also for the entire target branch. + * + * ### TODO: As the source and target must be in the same repository, we + * should share a single session, tracking the two URLs separately. */ + { + apr_pool_t *sesspool = svn_pool_create(scratch_pool); + svn_ra_session_t *source_session, *target_session; + svn_client__pathrev_t *pathrev; + svn_revnum_t start_rev, end_rev, youngest_rev = SVN_INVALID_REVNUM; + + if (! finding_merged) + { + SVN_ERR(svn_client__ra_session_from_path2(&target_session, &pathrev, + target_path_or_url, NULL, + target_peg_revision, + target_peg_revision, + ctx, sesspool)); + SVN_ERR(svn_client__get_history_as_mergeinfo(&target_history, NULL, + pathrev, + SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, + target_session, ctx, + scratch_pool)); + } + + SVN_ERR(svn_client__ra_session_from_path2(&source_session, &pathrev, + source_path_or_url, NULL, + source_peg_revision, + source_peg_revision, + ctx, sesspool)); + SVN_ERR(svn_client__get_revision_number(&start_rev, &youngest_rev, + ctx->wc_ctx, source_path_or_url, + source_session, + source_start_revision, + sesspool)); + SVN_ERR(svn_client__get_revision_number(&end_rev, &youngest_rev, + ctx->wc_ctx, source_path_or_url, + source_session, + source_end_revision, + sesspool)); + SVN_ERR(svn_client__get_history_as_mergeinfo(&source_history, NULL, + pathrev, + MAX(end_rev, start_rev), + MIN(end_rev, start_rev), + source_session, ctx, + scratch_pool)); + if (start_rev > end_rev) + oldest_revs_first = FALSE; + + /* Close the source and target sessions. */ + svn_pool_destroy(sesspool); + } + + /* Separate the explicit or inherited mergeinfo on TARGET_PATH_OR_URL, + and possibly its explicit subtree mergeinfo, into their + inheritable and non-inheritable parts. */ + master_noninheritable_rangelist = apr_array_make(scratch_pool, 64, + sizeof(svn_merge_range_t *)); + master_inheritable_rangelist = apr_array_make(scratch_pool, 64, + sizeof(svn_merge_range_t *)); + inheritable_subtree_merges = apr_hash_make(scratch_pool); + + iterpool = svn_pool_create(scratch_pool); + + for (hi_catalog = apr_hash_first(scratch_pool, target_mergeinfo_cat); + hi_catalog; + hi_catalog = apr_hash_next(hi_catalog)) + { + svn_mergeinfo_t subtree_mergeinfo = svn__apr_hash_index_val(hi_catalog); + svn_mergeinfo_t subtree_history; + svn_mergeinfo_t subtree_source_history; + svn_mergeinfo_t subtree_inheritable_mergeinfo; + svn_mergeinfo_t subtree_noninheritable_mergeinfo; + svn_mergeinfo_t merged_noninheritable; + svn_mergeinfo_t merged; + const char *subtree_path = svn__apr_hash_index_key(hi_catalog); + svn_boolean_t is_subtree = strcmp(subtree_path, + target_repos_relpath) != 0; + svn_pool_clear(iterpool); + + if (is_subtree) + { + /* If SUBTREE_PATH is a proper subtree of TARGET_PATH_OR_URL + then make a copy of SOURCE_HISTORY that is path adjusted + for the subtree. */ + const char *subtree_rel_path = + subtree_path + strlen(target_repos_relpath) + 1; + + SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo( + &subtree_source_history, source_history, + subtree_rel_path, scratch_pool, scratch_pool)); + + if (!finding_merged) + SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo( + &subtree_history, target_history, + subtree_rel_path, scratch_pool, scratch_pool)); + } + else + { + subtree_source_history = source_history; + if (!finding_merged) + subtree_history = target_history; + } + + if (!finding_merged) + { + svn_mergeinfo_t merged_via_history; + SVN_ERR(svn_mergeinfo_intersect2(&merged_via_history, + subtree_history, + subtree_source_history, TRUE, + scratch_pool, iterpool)); + SVN_ERR(svn_mergeinfo_merge2(subtree_mergeinfo, + merged_via_history, + scratch_pool, scratch_pool)); + } + + SVN_ERR(svn_mergeinfo_inheritable2(&subtree_inheritable_mergeinfo, + subtree_mergeinfo, NULL, + SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, + TRUE, scratch_pool, iterpool)); + SVN_ERR(svn_mergeinfo_inheritable2(&subtree_noninheritable_mergeinfo, + subtree_mergeinfo, NULL, + SVN_INVALID_REVNUM, + SVN_INVALID_REVNUM, + FALSE, scratch_pool, iterpool)); + + /* Find the intersection of the non-inheritable part of + SUBTREE_MERGEINFO and SOURCE_HISTORY. svn_mergeinfo_intersect2() + won't consider non-inheritable and inheritable ranges + intersecting unless we ignore inheritance, but in doing so the + resulting intersections have all inheritable ranges. To get + around this we set the inheritance on the result to all + non-inheritable. */ + SVN_ERR(svn_mergeinfo_intersect2(&merged_noninheritable, + subtree_noninheritable_mergeinfo, + subtree_source_history, FALSE, + scratch_pool, iterpool)); + svn_mergeinfo__set_inheritance(merged_noninheritable, FALSE, + scratch_pool); + + /* Keep track of all ranges partially merged to any and all + subtrees. */ + SVN_ERR(svn_rangelist__merge_many(master_noninheritable_rangelist, + merged_noninheritable, + scratch_pool, iterpool)); + + /* Find the intersection of the inheritable part of TGT_MERGEINFO + and SOURCE_HISTORY. */ + SVN_ERR(svn_mergeinfo_intersect2(&merged, + subtree_inheritable_mergeinfo, + subtree_source_history, FALSE, + scratch_pool, iterpool)); + + /* Keep track of all ranges fully merged to any and all + subtrees. */ + if (apr_hash_count(merged)) + { + /* The inheritable rangelist merged from SUBTREE_SOURCE_HISTORY + to SUBTREE_PATH. */ + svn_rangelist_t *subtree_merged_rangelist = + apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *)); + + SVN_ERR(svn_rangelist__merge_many(master_inheritable_rangelist, + merged, scratch_pool, iterpool)); + SVN_ERR(svn_rangelist__merge_many(subtree_merged_rangelist, + merged, scratch_pool, iterpool)); + + svn_hash_sets(inheritable_subtree_merges, subtree_path, + subtree_merged_rangelist); + } + else + { + /* Map SUBTREE_PATH to an empty rangelist if there was nothing + fully merged. e.g. Only empty or non-inheritable mergeinfo + on the subtree or mergeinfo unrelated to the source. */ + svn_hash_sets(inheritable_subtree_merges, subtree_path, + apr_array_make(scratch_pool, 0, + sizeof(svn_merge_range_t *))); + } + } + + /* Make sure every range in MASTER_INHERITABLE_RANGELIST is fully merged to + each subtree (including the target itself). Any revisions which don't + exist in *every* subtree are *potentially* only partially merged to the + tree rooted at TARGET_PATH_OR_URL, so move those revisions to + MASTER_NONINHERITABLE_RANGELIST. It may turn out that that a revision + was merged to the only subtree it affects, but we need to examine the + logs to make this determination (which will be done by + logs_for_mergeinfo_rangelist). */ + if (master_inheritable_rangelist->nelts) + { + for (hi = apr_hash_first(scratch_pool, inheritable_subtree_merges); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *deleted_rangelist; + svn_rangelist_t *added_rangelist; + svn_rangelist_t *subtree_merged_rangelist = + svn__apr_hash_index_val(hi); + + svn_pool_clear(iterpool); + + SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist, + master_inheritable_rangelist, + subtree_merged_rangelist, TRUE, + iterpool)); + + if (deleted_rangelist->nelts) + { + svn_rangelist__set_inheritance(deleted_rangelist, FALSE); + SVN_ERR(svn_rangelist_merge2(master_noninheritable_rangelist, + deleted_rangelist, + scratch_pool, iterpool)); + SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist, + deleted_rangelist, + master_inheritable_rangelist, + FALSE, + scratch_pool)); + } + } + } + + if (finding_merged) + { + /* Roll all the merged revisions into one rangelist. */ + SVN_ERR(svn_rangelist_merge2(master_inheritable_rangelist, + master_noninheritable_rangelist, + scratch_pool, scratch_pool)); + + } + else + { + /* Create the starting rangelist for what might be eligible. */ + svn_rangelist_t *source_master_rangelist = + apr_array_make(scratch_pool, 1, sizeof(svn_merge_range_t *)); + + SVN_ERR(svn_rangelist__merge_many(source_master_rangelist, + source_history, + scratch_pool, scratch_pool)); + + /* From what might be eligible subtract what we know is + partially merged and then merge that back. */ + SVN_ERR(svn_rangelist_remove(&source_master_rangelist, + master_noninheritable_rangelist, + source_master_rangelist, + FALSE, scratch_pool)); + SVN_ERR(svn_rangelist_merge2(source_master_rangelist, + master_noninheritable_rangelist, + scratch_pool, scratch_pool)); + SVN_ERR(svn_rangelist_remove(&master_inheritable_rangelist, + master_inheritable_rangelist, + source_master_rangelist, + TRUE, scratch_pool)); + } + + /* Nothing merged? Not even when considering shared history if + looking for eligible revisions (i.e. !FINDING_MERGED)? Then there + is nothing more to do. */ + if (! master_inheritable_rangelist->nelts) + { + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; + } + else + { + /* Determine the correct (youngest) target for 'svn log'. */ + svn_merge_range_t *youngest_range + = APR_ARRAY_IDX(master_inheritable_rangelist, + master_inheritable_rangelist->nelts - 1, + svn_merge_range_t *); + svn_rangelist_t *youngest_rangelist = + svn_rangelist__initialize(youngest_range->end - 1, + youngest_range->end, + youngest_range->inheritable, + scratch_pool);; + + for (hi = apr_hash_first(scratch_pool, source_history); + hi; + hi = apr_hash_next(hi)) + { + const char *key = svn__apr_hash_index_key(hi); + svn_rangelist_t *subtree_merged_rangelist = + svn__apr_hash_index_val(hi); + svn_rangelist_t *intersecting_rangelist; + + svn_pool_clear(iterpool); + SVN_ERR(svn_rangelist_intersect(&intersecting_rangelist, + youngest_rangelist, + subtree_merged_rangelist, + FALSE, iterpool)); + + APR_ARRAY_PUSH(merge_source_fspaths, const char *) = key; + + if (intersecting_rangelist->nelts) + log_target = key; + } + } + + svn_pool_destroy(iterpool); + + /* Step 4: Finally, we run 'svn log' to drive our log receiver, but + using a receiver filter to only allow revisions to pass through + that are in our rangelist. */ + log_target = svn_path_url_add_component2(repos_root, log_target + 1, + scratch_pool); + + SVN_ERR(logs_for_mergeinfo_rangelist(log_target, merge_source_fspaths, + finding_merged, + master_inheritable_rangelist, + oldest_revs_first, + target_mergeinfo_cat, + svn_fspath__join("/", + target_repos_relpath, + scratch_pool), + discover_changed_paths, + revprops, + log_receiver, log_receiver_baton, + ctx, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_suggest_merge_sources(apr_array_header_t **suggestions, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *repos_root; + const char *copyfrom_path; + apr_array_header_t *list; + svn_revnum_t copyfrom_rev; + svn_mergeinfo_catalog_t mergeinfo_cat; + svn_mergeinfo_t mergeinfo; + apr_hash_index_t *hi; + + list = apr_array_make(pool, 1, sizeof(const char *)); + + /* In our ideal algorithm, the list of recommendations should be + ordered by: + + 1. The most recent existing merge source. + 2. The copyfrom source (which will also be listed as a merge + source if the copy was made with a 1.5+ client and server). + 3. All other merge sources, most recent to least recent. + + However, determining the order of application of merge sources + requires a new RA API. Until such an API is available, our + algorithm will be: + + 1. The copyfrom source. + 2. All remaining merge sources (unordered). + */ + + /* ### TODO: Share ra_session batons to improve efficiency? */ + SVN_ERR(get_mergeinfo(&mergeinfo_cat, &repos_root, path_or_url, + peg_revision, FALSE, FALSE, ctx, pool, pool)); + + if (mergeinfo_cat && apr_hash_count(mergeinfo_cat)) + { + /* We asked only for the PATH_OR_URL's mergeinfo, not any of its + descendants. So if there is anything in the catalog it is the + mergeinfo for PATH_OR_URL. */ + mergeinfo = svn__apr_hash_index_val(apr_hash_first(pool, mergeinfo_cat)); + } + else + { + mergeinfo = NULL; + } + + SVN_ERR(svn_client__get_copy_source(©from_path, ©from_rev, + path_or_url, peg_revision, ctx, + pool, pool)); + if (copyfrom_path) + { + APR_ARRAY_PUSH(list, const char *) = + svn_path_url_add_component2(repos_root, copyfrom_path, pool); + } + + if (mergeinfo) + { + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + const char *rel_path = svn__apr_hash_index_key(hi); + + if (copyfrom_path == NULL || strcmp(rel_path, copyfrom_path) != 0) + APR_ARRAY_PUSH(list, const char *) = \ + svn_path_url_add_component2(repos_root, rel_path + 1, pool); + } + } + + *suggestions = list; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__mergeinfo_status(svn_boolean_t *mergeinfo_changes, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *propchanges; + int i; + + *mergeinfo_changes = FALSE; + + SVN_ERR(svn_wc_get_prop_diffs2(&propchanges, NULL, wc_ctx, + local_abspath, scratch_pool, scratch_pool)); + + for (i = 0; i < propchanges->nelts; i++) + { + svn_prop_t prop = APR_ARRAY_IDX(propchanges, i, svn_prop_t); + if (strcmp(prop.name, SVN_PROP_MERGEINFO) == 0) + { + *mergeinfo_changes = TRUE; + break; + } + } + + return SVN_NO_ERROR; +} |