Author: rinrab
Date: Sun Nov 24 23:32:26 2024
New Revision: 1922065
URL: http://svn.apache.org/viewvc?rev=1922065&view=rev
Log:
On the 'apply-processor' branch: (going to follow up r1922048)
Move filter_self_referential_mergeinfo() function back to the merge.c
file from merge_processor.c. Also move the split_mergeinfo_on_revision()
function there as a dependency.
I just noticed that we'll need it there.
Note: these functions are not being referenced from anywhere, since all the
usages are currently disabled using #if TODO_FILTER_MERGEINFO, so there are
no functional changes, only the move of unreferenced function.
* subversion/libsvn_client/merge_processor.c
(split_mergeinfo_on_revision,
filter_self_referential_mergeinfo): moved from...
* subversion/libsvn_client/merge.c
(split_mergeinfo_on_revision,
filter_self_referential_mergeinfo): ...moved to
Modified:
subversion/branches/apply-processor/subversion/libsvn_client/merge.c
subversion/branches/apply-processor/subversion/libsvn_client/merge_processor.c
Modified: subversion/branches/apply-processor/subversion/libsvn_client/merge.c
URL:
http://svn.apache.org/viewvc/subversion/branches/apply-processor/subversion/libsvn_client/merge.c?rev=1922065&r1=1922064&r2=1922065&view=diff
==============================================================================
--- subversion/branches/apply-processor/subversion/libsvn_client/merge.c
(original)
+++ subversion/branches/apply-processor/subversion/libsvn_client/merge.c Sun
Nov 24 23:32:26 2024
@@ -604,6 +604,380 @@ find_nearest_ancestor(const apr_array_he
return NULL;
}
+
+/* Helper for filter_self_referential_mergeinfo()
+
+ *MERGEINFO is a non-empty, non-null collection of mergeinfo.
+
+ Remove all mergeinfo from *MERGEINFO that describes revision ranges
+ greater than REVISION. Put a copy of any removed mergeinfo, allocated
+ in POOL, into *YOUNGER_MERGEINFO.
+
+ If no mergeinfo is removed from *MERGEINFO then *YOUNGER_MERGEINFO is set
+ to NULL. If all mergeinfo is removed from *MERGEINFO then *MERGEINFO is
+ set to NULL.
+ */
+static svn_error_t*
+split_mergeinfo_on_revision(svn_mergeinfo_t *younger_mergeinfo,
+ svn_mergeinfo_t *mergeinfo,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ *younger_mergeinfo = NULL;
+ for (hi = apr_hash_first(pool, *mergeinfo); hi; hi = apr_hash_next(hi))
+ {
+ int i;
+ const char *merge_source_path = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
+
+ svn_pool_clear(iterpool);
+
+ for (i = 0; i < rangelist->nelts; i++)
+ {
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+ if (range->end <= revision)
+ {
+ /* This entirely of this range is as old or older than
+ REVISION, so leave it in *MERGEINFO. */
+ continue;
+ }
+ else
+ {
+ /* Since the rangelists in svn_mergeinfo_t's are sorted in
+ increasing order we know that part or all of *this* range
+ and *all* of the remaining ranges in *RANGELIST are younger
+ than REVISION. Remove the younger rangelists from
+ *MERGEINFO and put them in *YOUNGER_MERGEINFO. */
+ int j;
+ svn_rangelist_t *younger_rangelist =
+ apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
+
+ for (j = i; j < rangelist->nelts; j++)
+ {
+ svn_merge_range_t *younger_range = svn_merge_range_dup(
+ APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *), pool);
+
+ /* REVISION might intersect with the first range where
+ range->end > REVISION. If that is the case then split
+ the current range into two, putting the younger half
+ into *YOUNGER_MERGEINFO and leaving the older half in
+ *MERGEINFO. */
+ if (j == i && range->start + 1 <= revision)
+ younger_range->start = range->end = revision;
+
+ APR_ARRAY_PUSH(younger_rangelist, svn_merge_range_t *) =
+ younger_range;
+ }
+
+ /* So far we've only been manipulating rangelists, now we
+ actually create *YOUNGER_MERGEINFO and then remove the older
+ ranges from *MERGEINFO */
+ if (!(*younger_mergeinfo))
+ *younger_mergeinfo = apr_hash_make(pool);
+ svn_hash_sets(*younger_mergeinfo, merge_source_path,
+ younger_rangelist);
+ SVN_ERR(svn_mergeinfo_remove2(mergeinfo, *younger_mergeinfo,
+ *mergeinfo, TRUE, pool, iterpool));
+ break; /* ...out of for (i = 0; i < rangelist->nelts; i++) */
+ }
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for merge_props_changed().
+
+ *PROPS is an array of svn_prop_t structures representing regular properties
+ to be added to the working copy TARGET_ABSPATH.
+
+ The merge source and target are assumed to be in the same repository.
+
+ Filter out mergeinfo property additions to TARGET_ABSPATH when
+ those additions refer to the same line of history as TARGET_ABSPATH as
+ described below.
+
+ Examine the added mergeinfo, looking at each range (or single rev)
+ of each source path. If a source_path/range refers to the same line of
+ history as TARGET_ABSPATH (pegged at its base revision), then filter out
+ that range. If the entire rangelist for a given path is filtered then
+ filter out the path as well.
+
+ RA_SESSION is an open RA session to the repository
+ in which both the source and target live, else RA_SESSION is not used. It
+ may be temporarily reparented as needed by this function.
+
+ Use CTX for any further client operations.
+
+ If any filtering occurs, set outgoing *PROPS to a shallow copy (allocated
+ in POOL) of incoming *PROPS minus the filtered mergeinfo. */
+static svn_error_t *
+filter_self_referential_mergeinfo(apr_array_header_t **props,
+ const char *target_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *adjusted_props;
+ int i;
+ apr_pool_t *iterpool;
+ svn_boolean_t is_copy;
+ const char *repos_relpath;
+ svn_client__pathrev_t target_base;
+
+ /* If PATH itself has been added there is no need to filter. */
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, &target_base.rev, &repos_relpath,
+ &target_base.repos_root_url,
+ &target_base.repos_uuid, NULL, NULL,
+ ctx->wc_ctx, target_abspath, FALSE,
+ pool, pool));
+
+ if (is_copy || !repos_relpath)
+ return SVN_NO_ERROR; /* A copy or a local addition */
+
+ target_base.url = svn_path_url_add_component2(target_base.repos_root_url,
+ repos_relpath, pool);
+
+ adjusted_props = apr_array_make(pool, (*props)->nelts, sizeof(svn_prop_t));
+ iterpool = svn_pool_create(pool);
+ for (i = 0; i < (*props)->nelts; ++i)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX((*props), i, svn_prop_t);
+
+ svn_mergeinfo_t mergeinfo, younger_mergeinfo;
+ svn_mergeinfo_t filtered_mergeinfo = NULL;
+ svn_mergeinfo_t filtered_younger_mergeinfo = NULL;
+ svn_error_t *err;
+
+ /* If this property isn't mergeinfo or is NULL valued (i.e. prop removal)
+ or empty mergeinfo it does not require any special handling. There
+ is nothing to filter out of empty mergeinfo and the concept of
+ filtering doesn't apply if we are trying to remove mergeinfo
+ entirely. */
+ if ((strcmp(prop->name, SVN_PROP_MERGEINFO) != 0)
+ || (! prop->value) /* Removal of mergeinfo */
+ || (! prop->value->len)) /* Empty mergeinfo */
+ {
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
+ continue;
+ }
+
+ svn_pool_clear(iterpool);
+
+ /* Non-empty mergeinfo; filter self-referential mergeinfo out. */
+
+ /* Parse the incoming mergeinfo to allow easier manipulation. */
+ err = svn_mergeinfo_parse(&mergeinfo, prop->value->data, iterpool);
+
+ if (err)
+ {
+ /* Issue #3896: If we can't parse it, we certainly can't
+ filter it. */
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
+ continue;
+ }
+ else
+ {
+ return svn_error_trace(err);
+ }
+ }
+
+ /* The working copy target PATH is at BASE_REVISION. Divide the
+ incoming mergeinfo into two groups. One where all revision ranges
+ are as old or older than BASE_REVISION and one where all revision
+ ranges are younger.
+
+ Note: You may be wondering why we do this.
+
+ For the incoming mergeinfo "older" than target's base revision we
+ can filter out self-referential mergeinfo efficiently using
+ svn_client__get_history_as_mergeinfo(). We simply look at PATH's
+ natural history as mergeinfo and remove that from any incoming
+ mergeinfo.
+
+ For mergeinfo "younger" than the base revision we can't use
+ svn_ra_get_location_segments() to look into PATH's future
+ history. Instead we must use svn_client__repos_locations() and
+ look at each incoming source/range individually and see if PATH
+ at its base revision and PATH at the start of the incoming range
+ exist on the same line of history. If they do then we can filter
+ out the incoming range. But since we have to do this for each
+ range there is a substantial performance penalty to pay if the
+ incoming ranges are not contiguous, i.e. we call
+ svn_client__repos_locations for each discrete range and incur
+ the cost of a roundtrip communication with the repository. */
+ SVN_ERR(split_mergeinfo_on_revision(&younger_mergeinfo,
+ &mergeinfo,
+ target_base.rev,
+ iterpool));
+
+ /* Filter self-referential mergeinfo from younger_mergeinfo. */
+ if (younger_mergeinfo)
+ {
+ apr_hash_index_t *hi;
+ const char *merge_source_root_url;
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session,
+ &merge_source_root_url, iterpool));
+
+ for (hi = apr_hash_first(iterpool, younger_mergeinfo);
+ hi; hi = apr_hash_next(hi))
+ {
+ int j;
+ const char *source_path = apr_hash_this_key(hi);
+ svn_rangelist_t *rangelist = apr_hash_this_val(hi);
+ const char *merge_source_url;
+ svn_rangelist_t *adjusted_rangelist =
+ apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
+
+ merge_source_url =
+ svn_path_url_add_component2(merge_source_root_url,
+ source_path + 1, iterpool);
+
+ for (j = 0; j < rangelist->nelts; j++)
+ {
+ svn_error_t *err2;
+ svn_client__pathrev_t *start_loc;
+ svn_merge_range_t *range =
+ APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *);
+
+ /* Because the merge source normalization code
+ ensures mergeinfo refers to real locations on
+ the same line of history, there's no need to
+ look at the whole range, just the start. */
+
+ /* Check if PATH@BASE_REVISION exists at
+ RANGE->START on the same line of history.
+ (start+1 because RANGE->start is not inclusive.) */
+ err2 = svn_client__repos_location(&start_loc, ra_session,
+ &target_base,
+ range->start + 1,
+ ctx, iterpool, iterpool);
+ if (err2)
+ {
+ if (err2->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES
+ || err2->apr_err == SVN_ERR_FS_NOT_FOUND
+ || err2->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
+ {
+ /* PATH@BASE_REVISION didn't exist at
+ RANGE->START + 1 or is unrelated to the
+ resource PATH@RANGE->START. Some of the
+ requested revisions may not even exist in
+ the repository; a real possibility since
+ mergeinfo is hand editable. In all of these
+ cases clear and ignore the error and don't
+ do any filtering.
+
+ Note: In this last case it is possible that
+ we will allow self-referential mergeinfo to
+ be applied, but fixing it here is potentially
+ very costly in terms of finding what part of
+ a range is actually valid. Simply allowing
+ the merge to proceed without filtering the
+ offending range seems the least worst
+ option. */
+ svn_error_clear(err2);
+ err2 = NULL;
+ APR_ARRAY_PUSH(adjusted_rangelist,
+ svn_merge_range_t *) = range;
+ }
+ else
+ {
+ return svn_error_trace(err2);
+ }
+ }
+ else
+ {
+ /* PATH@BASE_REVISION exists on the same
+ line of history at RANGE->START and RANGE->END.
+ Now check that PATH@BASE_REVISION's path
+ names at RANGE->START and RANGE->END are the same.
+ If the names are not the same then the mergeinfo
+ describing PATH@RANGE->START through
+ PATH@RANGE->END actually belong to some other
+ line of history and we want to record this
+ mergeinfo, not filter it. */
+ if (strcmp(start_loc->url, merge_source_url) != 0)
+ {
+ APR_ARRAY_PUSH(adjusted_rangelist,
+ svn_merge_range_t *) = range;
+ }
+ }
+ /* else no need to add, this mergeinfo is
+ all on the same line of history. */
+ } /* for (j = 0; j < rangelist->nelts; j++) */
+
+ /* Add any rangelists for source_path that are not
+ self-referential. */
+ if (adjusted_rangelist->nelts)
+ {
+ if (!filtered_younger_mergeinfo)
+ filtered_younger_mergeinfo = apr_hash_make(iterpool);
+ svn_hash_sets(filtered_younger_mergeinfo, source_path,
+ adjusted_rangelist);
+ }
+
+ } /* Iteration over each merge source in younger_mergeinfo. */
+ } /* if (younger_mergeinfo) */
+
+ /* Filter self-referential mergeinfo from "older" mergeinfo. */
+ if (mergeinfo)
+ {
+ svn_mergeinfo_t implicit_mergeinfo;
+
+ SVN_ERR(svn_client__get_history_as_mergeinfo(
+ &implicit_mergeinfo, NULL,
+ &target_base, target_base.rev, SVN_INVALID_REVNUM,
+ ra_session, ctx, iterpool));
+
+ /* Remove PATH's implicit mergeinfo from the incoming mergeinfo. */
+ SVN_ERR(svn_mergeinfo_remove2(&filtered_mergeinfo,
+ implicit_mergeinfo,
+ mergeinfo, TRUE, iterpool, iterpool));
+ }
+
+ /* Combine whatever older and younger filtered mergeinfo exists
+ into filtered_mergeinfo. */
+ if (filtered_mergeinfo && filtered_younger_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge2(filtered_mergeinfo,
+ filtered_younger_mergeinfo, iterpool,
+ iterpool));
+ else if (filtered_younger_mergeinfo)
+ filtered_mergeinfo = filtered_younger_mergeinfo;
+
+ /* If there is any incoming mergeinfo remaining after filtering
+ then put it in adjusted_props. */
+ if (filtered_mergeinfo && apr_hash_count(filtered_mergeinfo))
+ {
+ /* Convert filtered_mergeinfo to a svn_prop_t and put it
+ back in the array. */
+ svn_string_t *filtered_mergeinfo_str;
+ svn_prop_t *adjusted_prop = apr_pcalloc(pool,
+ sizeof(*adjusted_prop));
+ SVN_ERR(svn_mergeinfo_to_string(&filtered_mergeinfo_str,
+ filtered_mergeinfo,
+ pool));
+ adjusted_prop->name = SVN_PROP_MERGEINFO;
+ adjusted_prop->value = filtered_mergeinfo_str;
+ APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *adjusted_prop;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *props = adjusted_props;
+ return SVN_NO_ERROR;
+}
+
/* Find the highest level path in a merge target (possibly the merge target
itself) to use in a merge notification header.
Modified:
subversion/branches/apply-processor/subversion/libsvn_client/merge_processor.c
URL:
http://svn.apache.org/viewvc/subversion/branches/apply-processor/subversion/libsvn_client/merge_processor.c?rev=1922065&r1=1922064&r2=1922065&view=diff
==============================================================================
---
subversion/branches/apply-processor/subversion/libsvn_client/merge_processor.c
(original)
+++
subversion/branches/apply-processor/subversion/libsvn_client/merge_processor.c
Sun Nov 24 23:32:26 2024
@@ -233,405 +233,6 @@ make_conflict_versions(const svn_wc_conf
return SVN_NO_ERROR;
}
-/* Helper for filter_self_referential_mergeinfo()
-
- *MERGEINFO is a non-empty, non-null collection of mergeinfo.
-
- Remove all mergeinfo from *MERGEINFO that describes revision ranges
- greater than REVISION. Put a copy of any removed mergeinfo, allocated
- in POOL, into *YOUNGER_MERGEINFO.
-
- If no mergeinfo is removed from *MERGEINFO then *YOUNGER_MERGEINFO is set
- to NULL. If all mergeinfo is removed from *MERGEINFO then *MERGEINFO is
- set to NULL.
- */
-static svn_error_t*
-split_mergeinfo_on_revision(svn_mergeinfo_t *younger_mergeinfo,
- svn_mergeinfo_t *mergeinfo,
- svn_revnum_t revision,
- apr_pool_t *pool)
-{
- apr_hash_index_t *hi;
- apr_pool_t *iterpool = svn_pool_create(pool);
-
- *younger_mergeinfo = NULL;
- for (hi = apr_hash_first(pool, *mergeinfo); hi; hi = apr_hash_next(hi))
- {
- int i;
- const char *merge_source_path = apr_hash_this_key(hi);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi);
-
- svn_pool_clear(iterpool);
-
- for (i = 0; i < rangelist->nelts; i++)
- {
- svn_merge_range_t *range =
- APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
- if (range->end <= revision)
- {
- /* This entirely of this range is as old or older than
- REVISION, so leave it in *MERGEINFO. */
- continue;
- }
- else
- {
- /* Since the rangelists in svn_mergeinfo_t's are sorted in
- increasing order we know that part or all of *this* range
- and *all* of the remaining ranges in *RANGELIST are younger
- than REVISION. Remove the younger rangelists from
- *MERGEINFO and put them in *YOUNGER_MERGEINFO. */
- int j;
- svn_rangelist_t *younger_rangelist =
- apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
-
- for (j = i; j < rangelist->nelts; j++)
- {
- svn_merge_range_t *younger_range = svn_merge_range_dup(
- APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *), pool);
-
- /* REVISION might intersect with the first range where
- range->end > REVISION. If that is the case then split
- the current range into two, putting the younger half
- into *YOUNGER_MERGEINFO and leaving the older half in
- *MERGEINFO. */
- if (j == i && range->start + 1 <= revision)
- younger_range->start = range->end = revision;
-
- APR_ARRAY_PUSH(younger_rangelist, svn_merge_range_t *) =
- younger_range;
- }
-
- /* So far we've only been manipulating rangelists, now we
- actually create *YOUNGER_MERGEINFO and then remove the older
- ranges from *MERGEINFO */
- if (!(*younger_mergeinfo))
- *younger_mergeinfo = apr_hash_make(pool);
- svn_hash_sets(*younger_mergeinfo, merge_source_path,
- younger_rangelist);
- SVN_ERR(svn_mergeinfo_remove2(mergeinfo, *younger_mergeinfo,
- *mergeinfo, TRUE, pool, iterpool));
- break; /* ...out of for (i = 0; i < rangelist->nelts; i++) */
- }
- }
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-
-/* Make a copy of PROPCHANGES (array of svn_prop_t) into *TRIMMED_PROPCHANGES,
- omitting any svn:mergeinfo changes. */
-static svn_error_t *
-omit_mergeinfo_changes(apr_array_header_t **trimmed_propchanges,
- const apr_array_header_t *propchanges,
- apr_pool_t *result_pool)
-{
- int i;
-
- *trimmed_propchanges = apr_array_make(result_pool,
- propchanges->nelts,
- sizeof(svn_prop_t));
-
- for (i = 0; i < propchanges->nelts; ++i)
- {
- const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
-
- /* If this property is not svn:mergeinfo, then copy it. */
- if (strcmp(change->name, SVN_PROP_MERGEINFO) != 0)
- APR_ARRAY_PUSH(*trimmed_propchanges, svn_prop_t) = *change;
- }
-
- return SVN_NO_ERROR;
-}
-
-
-/* Helper for merge_props_changed().
-
- *PROPS is an array of svn_prop_t structures representing regular properties
- to be added to the working copy TARGET_ABSPATH.
-
- The merge source and target are assumed to be in the same repository.
-
- Filter out mergeinfo property additions to TARGET_ABSPATH when
- those additions refer to the same line of history as TARGET_ABSPATH as
- described below.
-
- Examine the added mergeinfo, looking at each range (or single rev)
- of each source path. If a source_path/range refers to the same line of
- history as TARGET_ABSPATH (pegged at its base revision), then filter out
- that range. If the entire rangelist for a given path is filtered then
- filter out the path as well.
-
- RA_SESSION is an open RA session to the repository
- in which both the source and target live, else RA_SESSION is not used. It
- may be temporarily reparented as needed by this function.
-
- Use CTX for any further client operations.
-
- If any filtering occurs, set outgoing *PROPS to a shallow copy (allocated
- in POOL) of incoming *PROPS minus the filtered mergeinfo. */
-static svn_error_t *
-filter_self_referential_mergeinfo(apr_array_header_t **props,
- const char *target_abspath,
- svn_ra_session_t *ra_session,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
-{
- apr_array_header_t *adjusted_props;
- int i;
- apr_pool_t *iterpool;
- svn_boolean_t is_copy;
- const char *repos_relpath;
- svn_client__pathrev_t target_base;
-
- /* If PATH itself has been added there is no need to filter. */
- SVN_ERR(svn_wc__node_get_origin(&is_copy, &target_base.rev, &repos_relpath,
- &target_base.repos_root_url,
- &target_base.repos_uuid, NULL, NULL,
- ctx->wc_ctx, target_abspath, FALSE,
- pool, pool));
-
- if (is_copy || !repos_relpath)
- return SVN_NO_ERROR; /* A copy or a local addition */
-
- target_base.url = svn_path_url_add_component2(target_base.repos_root_url,
- repos_relpath, pool);
-
- adjusted_props = apr_array_make(pool, (*props)->nelts, sizeof(svn_prop_t));
- iterpool = svn_pool_create(pool);
- for (i = 0; i < (*props)->nelts; ++i)
- {
- svn_prop_t *prop = &APR_ARRAY_IDX((*props), i, svn_prop_t);
-
- svn_mergeinfo_t mergeinfo, younger_mergeinfo;
- svn_mergeinfo_t filtered_mergeinfo = NULL;
- svn_mergeinfo_t filtered_younger_mergeinfo = NULL;
- svn_error_t *err;
-
- /* If this property isn't mergeinfo or is NULL valued (i.e. prop removal)
- or empty mergeinfo it does not require any special handling. There
- is nothing to filter out of empty mergeinfo and the concept of
- filtering doesn't apply if we are trying to remove mergeinfo
- entirely. */
- if ((strcmp(prop->name, SVN_PROP_MERGEINFO) != 0)
- || (! prop->value) /* Removal of mergeinfo */
- || (! prop->value->len)) /* Empty mergeinfo */
- {
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
- continue;
- }
-
- svn_pool_clear(iterpool);
-
- /* Non-empty mergeinfo; filter self-referential mergeinfo out. */
-
- /* Parse the incoming mergeinfo to allow easier manipulation. */
- err = svn_mergeinfo_parse(&mergeinfo, prop->value->data, iterpool);
-
- if (err)
- {
- /* Issue #3896: If we can't parse it, we certainly can't
- filter it. */
- if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
- {
- svn_error_clear(err);
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
- continue;
- }
- else
- {
- return svn_error_trace(err);
- }
- }
-
- /* The working copy target PATH is at BASE_REVISION. Divide the
- incoming mergeinfo into two groups. One where all revision ranges
- are as old or older than BASE_REVISION and one where all revision
- ranges are younger.
-
- Note: You may be wondering why we do this.
-
- For the incoming mergeinfo "older" than target's base revision we
- can filter out self-referential mergeinfo efficiently using
- svn_client__get_history_as_mergeinfo(). We simply look at PATH's
- natural history as mergeinfo and remove that from any incoming
- mergeinfo.
-
- For mergeinfo "younger" than the base revision we can't use
- svn_ra_get_location_segments() to look into PATH's future
- history. Instead we must use svn_client__repos_locations() and
- look at each incoming source/range individually and see if PATH
- at its base revision and PATH at the start of the incoming range
- exist on the same line of history. If they do then we can filter
- out the incoming range. But since we have to do this for each
- range there is a substantial performance penalty to pay if the
- incoming ranges are not contiguous, i.e. we call
- svn_client__repos_locations for each discrete range and incur
- the cost of a roundtrip communication with the repository. */
- SVN_ERR(split_mergeinfo_on_revision(&younger_mergeinfo,
- &mergeinfo,
- target_base.rev,
- iterpool));
-
- /* Filter self-referential mergeinfo from younger_mergeinfo. */
- if (younger_mergeinfo)
- {
- apr_hash_index_t *hi;
- const char *merge_source_root_url;
-
- SVN_ERR(svn_ra_get_repos_root2(ra_session,
- &merge_source_root_url, iterpool));
-
- for (hi = apr_hash_first(iterpool, younger_mergeinfo);
- hi; hi = apr_hash_next(hi))
- {
- int j;
- const char *source_path = apr_hash_this_key(hi);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi);
- const char *merge_source_url;
- svn_rangelist_t *adjusted_rangelist =
- apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
-
- merge_source_url =
- svn_path_url_add_component2(merge_source_root_url,
- source_path + 1, iterpool);
-
- for (j = 0; j < rangelist->nelts; j++)
- {
- svn_error_t *err2;
- svn_client__pathrev_t *start_loc;
- svn_merge_range_t *range =
- APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *);
-
- /* Because the merge source normalization code
- ensures mergeinfo refers to real locations on
- the same line of history, there's no need to
- look at the whole range, just the start. */
-
- /* Check if PATH@BASE_REVISION exists at
- RANGE->START on the same line of history.
- (start+1 because RANGE->start is not inclusive.) */
- err2 = svn_client__repos_location(&start_loc, ra_session,
- &target_base,
- range->start + 1,
- ctx, iterpool, iterpool);
- if (err2)
- {
- if (err2->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES
- || err2->apr_err == SVN_ERR_FS_NOT_FOUND
- || err2->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
- {
- /* PATH@BASE_REVISION didn't exist at
- RANGE->START + 1 or is unrelated to the
- resource PATH@RANGE->START. Some of the
- requested revisions may not even exist in
- the repository; a real possibility since
- mergeinfo is hand editable. In all of these
- cases clear and ignore the error and don't
- do any filtering.
-
- Note: In this last case it is possible that
- we will allow self-referential mergeinfo to
- be applied, but fixing it here is potentially
- very costly in terms of finding what part of
- a range is actually valid. Simply allowing
- the merge to proceed without filtering the
- offending range seems the least worst
- option. */
- svn_error_clear(err2);
- err2 = NULL;
- APR_ARRAY_PUSH(adjusted_rangelist,
- svn_merge_range_t *) = range;
- }
- else
- {
- return svn_error_trace(err2);
- }
- }
- else
- {
- /* PATH@BASE_REVISION exists on the same
- line of history at RANGE->START and RANGE->END.
- Now check that PATH@BASE_REVISION's path
- names at RANGE->START and RANGE->END are the same.
- If the names are not the same then the mergeinfo
- describing PATH@RANGE->START through
- PATH@RANGE->END actually belong to some other
- line of history and we want to record this
- mergeinfo, not filter it. */
- if (strcmp(start_loc->url, merge_source_url) != 0)
- {
- APR_ARRAY_PUSH(adjusted_rangelist,
- svn_merge_range_t *) = range;
- }
- }
- /* else no need to add, this mergeinfo is
- all on the same line of history. */
- } /* for (j = 0; j < rangelist->nelts; j++) */
-
- /* Add any rangelists for source_path that are not
- self-referential. */
- if (adjusted_rangelist->nelts)
- {
- if (!filtered_younger_mergeinfo)
- filtered_younger_mergeinfo = apr_hash_make(iterpool);
- svn_hash_sets(filtered_younger_mergeinfo, source_path,
- adjusted_rangelist);
- }
-
- } /* Iteration over each merge source in younger_mergeinfo. */
- } /* if (younger_mergeinfo) */
-
- /* Filter self-referential mergeinfo from "older" mergeinfo. */
- if (mergeinfo)
- {
- svn_mergeinfo_t implicit_mergeinfo;
-
- SVN_ERR(svn_client__get_history_as_mergeinfo(
- &implicit_mergeinfo, NULL,
- &target_base, target_base.rev, SVN_INVALID_REVNUM,
- ra_session, ctx, iterpool));
-
- /* Remove PATH's implicit mergeinfo from the incoming mergeinfo. */
- SVN_ERR(svn_mergeinfo_remove2(&filtered_mergeinfo,
- implicit_mergeinfo,
- mergeinfo, TRUE, iterpool, iterpool));
- }
-
- /* Combine whatever older and younger filtered mergeinfo exists
- into filtered_mergeinfo. */
- if (filtered_mergeinfo && filtered_younger_mergeinfo)
- SVN_ERR(svn_mergeinfo_merge2(filtered_mergeinfo,
- filtered_younger_mergeinfo, iterpool,
- iterpool));
- else if (filtered_younger_mergeinfo)
- filtered_mergeinfo = filtered_younger_mergeinfo;
-
- /* If there is any incoming mergeinfo remaining after filtering
- then put it in adjusted_props. */
- if (filtered_mergeinfo && apr_hash_count(filtered_mergeinfo))
- {
- /* Convert filtered_mergeinfo to a svn_prop_t and put it
- back in the array. */
- svn_string_t *filtered_mergeinfo_str;
- svn_prop_t *adjusted_prop = apr_pcalloc(pool,
- sizeof(*adjusted_prop));
- SVN_ERR(svn_mergeinfo_to_string(&filtered_mergeinfo_str,
- filtered_mergeinfo,
- pool));
- adjusted_prop->name = SVN_PROP_MERGEINFO;
- adjusted_prop->value = filtered_mergeinfo_str;
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *adjusted_prop;
- }
- }
- svn_pool_destroy(iterpool);
-
- *props = adjusted_props;
- return SVN_NO_ERROR;
-}
-
/* Prepare a set of property changes PROPCHANGES to be used for a merge
operation on LOCAL_ABSPATH.