summaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/mergeinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/mergeinfo.c')
-rw-r--r--subversion/libsvn_client/mergeinfo.c2191
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(&copyfrom_path, &copyfrom_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;
+}