diff options
Diffstat (limited to 'subversion/libsvn_subr/mergeinfo.c')
-rw-r--r-- | subversion/libsvn_subr/mergeinfo.c | 2631 |
1 files changed, 2631 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/mergeinfo.c b/subversion/libsvn_subr/mergeinfo.c new file mode 100644 index 0000000..63496ae --- /dev/null +++ b/subversion/libsvn_subr/mergeinfo.c @@ -0,0 +1,2631 @@ +/* + * mergeinfo.c: Mergeinfo parsing and handling + * + * ==================================================================== + * 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 <assert.h> +#include <ctype.h> + +#include "svn_path.h" +#include "svn_types.h" +#include "svn_ctype.h" +#include "svn_pools.h" +#include "svn_sorts.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_string.h" +#include "svn_mergeinfo.h" +#include "private/svn_fspath.h" +#include "private/svn_mergeinfo_private.h" +#include "private/svn_string_private.h" +#include "private/svn_subr_private.h" +#include "svn_private_config.h" +#include "svn_hash.h" +#include "private/svn_dep_compat.h" + +/* Attempt to combine two ranges, IN1 and IN2. If they are adjacent or + overlapping, and their inheritability allows them to be combined, put + the result in OUTPUT and return TRUE, otherwise return FALSE. + + CONSIDER_INHERITANCE determines how to account for the inheritability + of IN1 and IN2 when trying to combine ranges. If ranges with different + inheritability are combined (CONSIDER_INHERITANCE must be FALSE for this + to happen) the result is inheritable. If both ranges are inheritable the + result is inheritable. Only and if both ranges are non-inheritable is + the result is non-inheritable. + + Range overlapping detection algorithm from + http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap +*/ +static svn_boolean_t +combine_ranges(svn_merge_range_t *output, + const svn_merge_range_t *in1, + const svn_merge_range_t *in2, + svn_boolean_t consider_inheritance) +{ + if (in1->start <= in2->end && in2->start <= in1->end) + { + if (!consider_inheritance + || (consider_inheritance + && (in1->inheritable == in2->inheritable))) + { + output->start = MIN(in1->start, in2->start); + output->end = MAX(in1->end, in2->end); + output->inheritable = (in1->inheritable || in2->inheritable); + return TRUE; + } + } + return FALSE; +} + +/* pathname -> PATHNAME */ +static svn_error_t * +parse_pathname(const char **input, + const char *end, + const char **pathname, + apr_pool_t *pool) +{ + const char *curr = *input; + const char *last_colon = NULL; + + /* A pathname may contain colons, so find the last colon before END + or newline. We'll consider this the divider between the pathname + and the revisionlist. */ + while (curr < end && *curr != '\n') + { + if (*curr == ':') + last_colon = curr; + curr++; + } + + if (!last_colon) + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Pathname not terminated by ':'")); + if (last_colon == *input) + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("No pathname preceding ':'")); + + /* Tolerate relative repository paths, but convert them to absolute. + ### Efficiency? 1 string duplication here, 2 in canonicalize. */ + *pathname = svn_fspath__canonicalize(apr_pstrndup(pool, *input, + last_colon - *input), + pool); + + *input = last_colon; + + return SVN_NO_ERROR; +} + +/* Return TRUE iff (svn_merge_range_t *) RANGE describes a valid, forward + * revision range. + * + * Note: The smallest valid value of RANGE->start is 0 because it is an + * exclusive endpoint, being one less than the revision number of the first + * change described by the range, and the oldest possible change is "r1" as + * there cannot be a change "r0". */ +#define IS_VALID_FORWARD_RANGE(range) \ + (SVN_IS_VALID_REVNUM((range)->start) && ((range)->start < (range)->end)) + +/* Ways in which two svn_merge_range_t can intersect or adjoin, if at all. */ +typedef enum intersection_type_t +{ + /* Ranges don't intersect and don't adjoin. */ + svn__no_intersection, + + /* Ranges are equal. */ + svn__equal_intersection, + + /* Ranges adjoin but don't overlap. */ + svn__adjoining_intersection, + + /* Ranges overlap but neither is a subset of the other. */ + svn__overlapping_intersection, + + /* One range is a proper subset of the other. */ + svn__proper_subset_intersection +} intersection_type_t; + +/* Given ranges R1 and R2, both of which must be forward merge ranges, + set *INTERSECTION_TYPE to describe how the ranges intersect, if they + do at all. The inheritance type of the ranges is not considered. */ +static svn_error_t * +get_type_of_intersection(const svn_merge_range_t *r1, + const svn_merge_range_t *r2, + intersection_type_t *intersection_type) +{ + SVN_ERR_ASSERT(r1); + SVN_ERR_ASSERT(r2); + SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r1)); + SVN_ERR_ASSERT(IS_VALID_FORWARD_RANGE(r2)); + + if (!(r1->start <= r2->end && r2->start <= r1->end)) + *intersection_type = svn__no_intersection; + else if (r1->start == r2->start && r1->end == r2->end) + *intersection_type = svn__equal_intersection; + else if (r1->end == r2->start || r2->end == r1->start) + *intersection_type = svn__adjoining_intersection; + else if (r1->start <= r2->start && r1->end >= r2->end) + *intersection_type = svn__proper_subset_intersection; + else if (r2->start <= r1->start && r2->end >= r1->end) + *intersection_type = svn__proper_subset_intersection; + else + *intersection_type = svn__overlapping_intersection; + + return SVN_NO_ERROR; +} + +/* Modify or extend RANGELIST (a list of merge ranges) to incorporate + NEW_RANGE. RANGELIST is a "rangelist" as defined in svn_mergeinfo.h. + + OVERVIEW + + Determine the minimal set of non-overlapping merge ranges required to + represent the combination of RANGELIST and NEW_RANGE. The result depends + on whether and how NEW_RANGE overlaps any merge range[*] in RANGELIST, + and also on any differences in the inheritability of each range, + according to the rules described below. Modify RANGELIST to represent + this result, by adjusting the last range in it and/or appending one or + two more ranges. + + ([*] Due to the simplifying assumption below, only the last range in + RANGELIST is considered.) + + DETAILS + + If RANGELIST is not empty assume NEW_RANGE does not intersect with any + range before the last one in RANGELIST. + + If RANGELIST is empty or NEW_RANGE does not intersect with the lastrange + in RANGELIST, then append a copy of NEW_RANGE, allocated in RESULT_POOL, + to RANGELIST. + + If NEW_RANGE intersects with the last range in RANGELIST then combine + these two ranges as described below: + + If the intersecting ranges have the same inheritability then simply + combine the ranges in place. Otherwise, if the ranges intersect but + differ in inheritability, then merge the ranges as dictated by + CONSIDER_INHERITANCE: + + If CONSIDER_INHERITANCE is false then intersecting ranges are combined + into a single range. The inheritability of the resulting range is + non-inheritable *only* if both ranges are non-inheritable, otherwise the + combined range is inheritable, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10* 6-13 4-13 + 4-10 6-13* 4-13 + 4-10* 6-13* 4-13* + + If CONSIDER_INHERITANCE is true, then only the intersection between the + two ranges is combined, with the inheritability of the resulting range + non-inheritable only if both ranges were non-inheritable. The + non-intersecting portions are added as separate ranges allocated in + RESULT_POOL, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10* 6 4-5*, 6, 7-10* + 4-10* 6-12 4-5*, 6-12 + + Note that the standard rules for rangelists still apply and overlapping + ranges are not allowed. So if the above would result in overlapping + ranges of the same inheritance, the overlapping ranges are merged into a + single range, e.g.: + + Last range in NEW_RANGE RESULTING RANGES + RANGELIST + ------------- --------- ---------------- + 4-10 6* 4-10 (Not 4-5, 6, 7-10) + + When replacing the last range in RANGELIST, either allocate a new range in + RESULT_POOL or modify the existing range in place. Any new ranges added + to RANGELIST are allocated in RESULT_POOL. +*/ +static svn_error_t * +combine_with_lastrange(const svn_merge_range_t *new_range, + svn_rangelist_t *rangelist, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool) +{ + svn_merge_range_t *lastrange; + svn_merge_range_t combined_range; + + /* We don't accept a NULL RANGELIST. */ + SVN_ERR_ASSERT(rangelist); + + if (rangelist->nelts > 0) + lastrange = APR_ARRAY_IDX(rangelist, rangelist->nelts - 1, svn_merge_range_t *); + else + lastrange = NULL; + + if (!lastrange) + { + /* No *LASTRANGE so push NEW_RANGE onto RANGELIST and we are done. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + } + else if (!consider_inheritance) + { + /* We are not considering inheritance so we can merge intersecting + ranges of different inheritability. Of course if the ranges + don't intersect at all we simply push NEW_RANGE only RANGELIST. */ + if (combine_ranges(&combined_range, lastrange, new_range, FALSE)) + { + *lastrange = combined_range; + } + else + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + } + } + else /* Considering inheritance */ + { + if (combine_ranges(&combined_range, lastrange, new_range, TRUE)) + { + /* Even when considering inheritance two intersection ranges + of the same inheritability can simply be combined. */ + *lastrange = combined_range; + } + else + { + /* If we are here then the ranges either don't intersect or do + intersect but have differing inheritability. Check for the + first case as that is easy to handle. */ + intersection_type_t intersection_type; + svn_boolean_t sorted = FALSE; + + SVN_ERR(get_type_of_intersection(new_range, lastrange, + &intersection_type)); + + switch (intersection_type) + { + case svn__no_intersection: + /* NEW_RANGE and *LASTRANGE *really* don't intersect so + just push NEW_RANGE only RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + sorted = (svn_sort_compare_ranges(&lastrange, + &new_range) < 0); + break; + + case svn__equal_intersection: + /* They range are equal so all we do is force the + inheritability of lastrange to true. */ + lastrange->inheritable = TRUE; + sorted = TRUE; + break; + + case svn__adjoining_intersection: + /* They adjoin but don't overlap so just push NEW_RANGE + onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = + svn_merge_range_dup(new_range, result_pool); + sorted = (svn_sort_compare_ranges(&lastrange, + &new_range) < 0); + break; + + case svn__overlapping_intersection: + /* They ranges overlap but neither is a proper subset of + the other. We'll end up pusing two new ranges onto + RANGELIST, the intersecting part and the part unique to + NEW_RANGE.*/ + { + svn_merge_range_t *r1 = svn_merge_range_dup(lastrange, + result_pool); + svn_merge_range_t *r2 = svn_merge_range_dup(new_range, + result_pool); + + /* Pop off *LASTRANGE to make our manipulations + easier. */ + apr_array_pop(rangelist); + + /* Ensure R1 is the older range. */ + if (r2->start < r1->start) + { + /* Swap R1 and R2. */ + *r2 = *r1; + *r1 = *new_range; + } + + /* Absorb the intersecting ranges into the + inheritable range. */ + if (r1->inheritable) + r2->start = r1->end; + else + r1->end = r2->start; + + /* Push everything back onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1; + sorted = (svn_sort_compare_ranges(&lastrange, + &r1) < 0); + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2; + if (sorted) + sorted = (svn_sort_compare_ranges(&r1, &r2) < 0); + break; + } + + default: /* svn__proper_subset_intersection */ + { + /* One range is a proper subset of the other. */ + svn_merge_range_t *r1 = svn_merge_range_dup(lastrange, + result_pool); + svn_merge_range_t *r2 = svn_merge_range_dup(new_range, + result_pool); + svn_merge_range_t *r3 = NULL; + + /* Pop off *LASTRANGE to make our manipulations + easier. */ + apr_array_pop(rangelist); + + /* Ensure R1 is the superset. */ + if (r2->start < r1->start || r2->end > r1->end) + { + /* Swap R1 and R2. */ + *r2 = *r1; + *r1 = *new_range; + } + + if (r1->inheritable) + { + /* The simple case: The superset is inheritable, so + just combine r1 and r2. */ + r1->start = MIN(r1->start, r2->start); + r1->end = MAX(r1->end, r2->end); + r2 = NULL; + } + else if (r1->start == r2->start) + { + svn_revnum_t tmp_revnum; + + /* *LASTRANGE and NEW_RANGE share an end point. */ + tmp_revnum = r1->end; + r1->end = r2->end; + r2->inheritable = r1->inheritable; + r1->inheritable = TRUE; + r2->start = r1->end; + r2->end = tmp_revnum; + } + else if (r1->end == r2->end) + { + /* *LASTRANGE and NEW_RANGE share an end point. */ + r1->end = r2->start; + r2->inheritable = TRUE; + } + else + { + /* NEW_RANGE and *LASTRANGE share neither start + nor end points. */ + r3 = apr_pcalloc(result_pool, sizeof(*r3)); + r3->start = r2->end; + r3->end = r1->end; + r3->inheritable = r1->inheritable; + r2->inheritable = TRUE; + r1->end = r2->start; + } + + /* Push everything back onto RANGELIST. */ + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r1; + sorted = (svn_sort_compare_ranges(&lastrange, &r1) < 0); + if (r2) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r2; + if (sorted) + sorted = (svn_sort_compare_ranges(&r1, &r2) < 0); + } + if (r3) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = r3; + if (sorted) + { + if (r2) + sorted = (svn_sort_compare_ranges(&r2, + &r3) < 0); + else + sorted = (svn_sort_compare_ranges(&r1, + &r3) < 0); + } + } + break; + } + } + + /* Some of the above cases might have put *RANGELIST out of + order, so re-sort.*/ + if (!sorted) + qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size, + svn_sort_compare_ranges); + } + } + + return SVN_NO_ERROR; +} + +/* Convert a single svn_merge_range_t *RANGE back into a string. */ +static char * +range_to_string(const svn_merge_range_t *range, + apr_pool_t *pool) +{ + const char *mark + = range->inheritable ? "" : SVN_MERGEINFO_NONINHERITABLE_STR; + + if (range->start == range->end - 1) + return apr_psprintf(pool, "%ld%s", range->end, mark); + else if (range->start - 1 == range->end) + return apr_psprintf(pool, "-%ld%s", range->start, mark); + else if (range->start < range->end) + return apr_psprintf(pool, "%ld-%ld%s", range->start + 1, range->end, mark); + else + return apr_psprintf(pool, "%ld-%ld%s", range->start, range->end + 1, mark); +} + +/* Helper for svn_mergeinfo_parse() + Append revision ranges onto the array RANGELIST to represent the range + descriptions found in the string *INPUT. Read only as far as a newline + or the position END, whichever comes first. Set *INPUT to the position + after the last character of INPUT that was used. + + revisionlist -> (revisionelement)(COMMA revisionelement)* + revisionrange -> REVISION "-" REVISION("*") + revisionelement -> revisionrange | REVISION("*") +*/ +static svn_error_t * +parse_rangelist(const char **input, const char *end, + svn_rangelist_t *rangelist, + apr_pool_t *pool) +{ + const char *curr = *input; + + /* Eat any leading horizontal white-space before the rangelist. */ + while (curr < end && *curr != '\n' && isspace(*curr)) + curr++; + + if (*curr == '\n' || curr == end) + { + /* Empty range list. */ + *input = curr; + return SVN_NO_ERROR; + } + + while (curr < end && *curr != '\n') + { + /* Parse individual revisions or revision ranges. */ + svn_merge_range_t *mrange = apr_pcalloc(pool, sizeof(*mrange)); + svn_revnum_t firstrev; + + SVN_ERR(svn_revnum_parse(&firstrev, curr, &curr)); + if (*curr != '-' && *curr != '\n' && *curr != ',' && *curr != '*' + && curr != end) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in revision " + "list"), *curr); + mrange->start = firstrev - 1; + mrange->end = firstrev; + mrange->inheritable = TRUE; + + if (firstrev == 0) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid revision number '0' found in " + "range list")); + + if (*curr == '-') + { + svn_revnum_t secondrev; + + curr++; + SVN_ERR(svn_revnum_parse(&secondrev, curr, &curr)); + if (firstrev > secondrev) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse reversed revision " + "range '%ld-%ld'"), + firstrev, secondrev); + else if (firstrev == secondrev) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse revision range " + "'%ld-%ld' with same start and end " + "revisions"), firstrev, secondrev); + mrange->end = secondrev; + } + + if (*curr == '\n' || curr == end) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + *input = curr; + return SVN_NO_ERROR; + } + else if (*curr == ',') + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + curr++; + } + else if (*curr == '*') + { + mrange->inheritable = FALSE; + curr++; + if (*curr == ',' || *curr == '\n' || curr == end) + { + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = mrange; + if (*curr == ',') + { + curr++; + } + else + { + *input = curr; + return SVN_NO_ERROR; + } + } + else + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in " + "range list"), *curr); + } + } + else + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Invalid character '%c' found in " + "range list"), *curr); + } + + } + if (*curr != '\n') + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Range list parsing ended before hitting " + "newline")); + *input = curr; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__parse(svn_rangelist_t **rangelist, + const char *str, + apr_pool_t *result_pool) +{ + const char *s = str; + + *rangelist = apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *)); + SVN_ERR(parse_rangelist(&s, s + strlen(s), *rangelist, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__combine_adjacent_ranges(svn_rangelist_t *rangelist, + apr_pool_t *scratch_pool) +{ + int i; + svn_merge_range_t *range, *lastrange; + + lastrange = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); + + for (i = 1; i < rangelist->nelts; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + if (lastrange->start <= range->end + && range->start <= lastrange->end) + { + /* The ranges are adjacent or intersect. */ + + /* svn_mergeinfo_parse promises to combine overlapping + ranges as long as their inheritability is the same. */ + if (range->start < lastrange->end + && range->inheritable != lastrange->inheritable) + { + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Unable to parse overlapping " + "revision ranges '%s' and '%s' " + "with different inheritance " + "types"), + range_to_string(lastrange, + scratch_pool), + range_to_string(range, + scratch_pool)); + } + + /* Combine overlapping or adjacent ranges with the + same inheritability. */ + if (lastrange->inheritable == range->inheritable) + { + lastrange->end = MAX(range->end, lastrange->end); + svn_sort__array_delete(rangelist, i, 1); + i--; + } + } + lastrange = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + } + + return SVN_NO_ERROR; +} + +/* revisionline -> PATHNAME COLON revisionlist */ +static svn_error_t * +parse_revision_line(const char **input, const char *end, svn_mergeinfo_t hash, + apr_pool_t *scratch_pool) +{ + const char *pathname = ""; + apr_ssize_t klen; + svn_rangelist_t *existing_rangelist; + svn_rangelist_t *rangelist = apr_array_make(scratch_pool, 1, + sizeof(svn_merge_range_t *)); + + SVN_ERR(parse_pathname(input, end, &pathname, scratch_pool)); + + if (*(*input) != ':') + return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Pathname not terminated by ':'")); + + *input = *input + 1; + + SVN_ERR(parse_rangelist(input, end, rangelist, scratch_pool)); + + if (rangelist->nelts == 0) + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Mergeinfo for '%s' maps to an " + "empty revision range"), pathname); + if (*input != end && *(*input) != '\n') + return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL, + _("Could not find end of line in range list line " + "in '%s'"), *input); + + if (*input != end) + *input = *input + 1; + + /* Sort the rangelist, combine adjacent ranges into single ranges, + and make sure there are no overlapping ranges. */ + if (rangelist->nelts > 1) + { + qsort(rangelist->elts, rangelist->nelts, rangelist->elt_size, + svn_sort_compare_ranges); + + SVN_ERR(svn_rangelist__combine_adjacent_ranges(rangelist, scratch_pool)); + } + + /* Handle any funky mergeinfo with relative merge source paths that + might exist due to issue #3547. It's possible that this issue allowed + the creation of mergeinfo with path keys that differ only by a + leading slash, e.g. "trunk:4033\n/trunk:4039-4995". In the event + we encounter this we merge the rangelists together under a single + absolute path key. */ + klen = strlen(pathname); + existing_rangelist = apr_hash_get(hash, pathname, klen); + if (existing_rangelist) + SVN_ERR(svn_rangelist_merge2(rangelist, existing_rangelist, + scratch_pool, scratch_pool)); + + apr_hash_set(hash, apr_pstrmemdup(apr_hash_pool_get(hash), pathname, klen), + klen, svn_rangelist_dup(rangelist, apr_hash_pool_get(hash))); + + return SVN_NO_ERROR; +} + +/* top -> revisionline (NEWLINE revisionline)* */ +static svn_error_t * +parse_top(const char **input, const char *end, svn_mergeinfo_t hash, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + + while (*input < end) + { + svn_pool_clear(iterpool); + SVN_ERR(parse_revision_line(input, end, hash, iterpool)); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_parse(svn_mergeinfo_t *mergeinfo, + const char *input, + apr_pool_t *pool) +{ + svn_error_t *err; + + *mergeinfo = svn_hash__make(pool); + err = parse_top(&input, input + strlen(input), *mergeinfo, pool); + + /* Always return SVN_ERR_MERGEINFO_PARSE_ERROR as the topmost error. */ + if (err && err->apr_err != SVN_ERR_MERGEINFO_PARSE_ERROR) + err = svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, err, + _("Could not parse mergeinfo string '%s'"), + input); + return err; +} + +/* Cleanup after svn_rangelist_merge2 when it modifies the ending range of + a single rangelist element in-place. + + If *RANGE_INDEX is not a valid element in RANGELIST do nothing. Otherwise + ensure that RANGELIST[*RANGE_INDEX]->END does not adjoin or overlap any + subsequent ranges in RANGELIST. + + If overlap is found, then remove, modify, and/or add elements to RANGELIST + as per the invariants for rangelists documented in svn_mergeinfo.h. If + RANGELIST[*RANGE_INDEX]->END adjoins a subsequent element then combine the + elements if their inheritability permits -- The inheritance of intersecting + and adjoining ranges is handled as per svn_mergeinfo_merge2. Upon return + set *RANGE_INDEX to the index of the youngest element modified, added, or + adjoined to RANGELIST[*RANGE_INDEX]. + + Note: Adjoining rangelist elements are those where the end rev of the older + element is equal to the start rev of the younger element. + + Any new elements inserted into RANGELIST are allocated in RESULT_POOL.*/ +static void +adjust_remaining_ranges(svn_rangelist_t *rangelist, + int *range_index, + apr_pool_t *result_pool) +{ + int i; + int starting_index; + int elements_to_delete = 0; + svn_merge_range_t *modified_range; + + if (*range_index >= rangelist->nelts) + return; + + starting_index = *range_index + 1; + modified_range = APR_ARRAY_IDX(rangelist, *range_index, svn_merge_range_t *); + + for (i = *range_index + 1; i < rangelist->nelts; i++) + { + svn_merge_range_t *next_range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + + /* If MODIFIED_RANGE doesn't adjoin or overlap the next range in + RANGELIST then we are finished. */ + if (modified_range->end < next_range->start) + break; + + /* Does MODIFIED_RANGE adjoin NEXT_RANGE? */ + if (modified_range->end == next_range->start) + { + if (modified_range->inheritable == next_range->inheritable) + { + /* Combine adjoining ranges with the same inheritability. */ + modified_range->end = next_range->end; + elements_to_delete++; + } + else + { + /* Cannot join because inheritance differs. */ + (*range_index)++; + } + break; + } + + /* Alright, we know MODIFIED_RANGE overlaps NEXT_RANGE, but how? */ + if (modified_range->end > next_range->end) + { + /* NEXT_RANGE is a proper subset of MODIFIED_RANGE and the two + don't share the same end range. */ + if (modified_range->inheritable + || (modified_range->inheritable == next_range->inheritable)) + { + /* MODIFIED_RANGE absorbs NEXT_RANGE. */ + elements_to_delete++; + } + else + { + /* NEXT_RANGE is a proper subset MODIFIED_RANGE but + MODIFIED_RANGE is non-inheritable and NEXT_RANGE is + inheritable. This means MODIFIED_RANGE is truncated, + NEXT_RANGE remains, and the portion of MODIFIED_RANGE + younger than NEXT_RANGE is added as a separate range: + ______________________________________________ + | | + M MODIFIED_RANGE N + | (!inhertiable) | + |______________________________________________| + | | + O NEXT_RANGE P + | (inheritable)| + |______________| + | + V + _______________________________________________ + | | | | + M MODIFIED_RANGE O NEXT_RANGE P NEW_RANGE N + | (!inhertiable) | (inheritable)| (!inheritable)| + |________________|______________|_______________| + */ + svn_merge_range_t *new_modified_range = + apr_palloc(result_pool, sizeof(*new_modified_range)); + new_modified_range->start = next_range->end; + new_modified_range->end = modified_range->end; + new_modified_range->inheritable = FALSE; + modified_range->end = next_range->start; + (*range_index)+=2; + svn_sort__array_insert(&new_modified_range, rangelist, + *range_index); + /* Recurse with the new range. */ + adjust_remaining_ranges(rangelist, range_index, result_pool); + break; + } + } + else if (modified_range->end == next_range->end) + { + /* NEXT_RANGE is a proper subset MODIFIED_RANGE and share + the same end range. */ + if (modified_range->inheritable + || (modified_range->inheritable == next_range->inheritable)) + { + /* MODIFIED_RANGE absorbs NEXT_RANGE. */ + elements_to_delete++; + } + else + { + /* The intersection between MODIFIED_RANGE and NEXT_RANGE is + absorbed by the latter. */ + modified_range->end = next_range->start; + (*range_index)++; + } + break; + } + else + { + /* NEXT_RANGE and MODIFIED_RANGE intersect but NEXT_RANGE is not + a proper subset of MODIFIED_RANGE, nor do the two share the + same end revision, i.e. they overlap. */ + if (modified_range->inheritable == next_range->inheritable) + { + /* Combine overlapping ranges with the same inheritability. */ + modified_range->end = next_range->end; + elements_to_delete++; + } + else if (modified_range->inheritable) + { + /* MODIFIED_RANGE absorbs the portion of NEXT_RANGE it overlaps + and NEXT_RANGE is truncated. */ + next_range->start = modified_range->end; + (*range_index)++; + } + else + { + /* NEXT_RANGE absorbs the portion of MODIFIED_RANGE it overlaps + and MODIFIED_RANGE is truncated. */ + modified_range->end = next_range->start; + (*range_index)++; + } + break; + } + } + + if (elements_to_delete) + svn_sort__array_delete(rangelist, starting_index, elements_to_delete); +} + +svn_error_t * +svn_rangelist_merge2(svn_rangelist_t *rangelist, + const svn_rangelist_t *changes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i = 0; + int j = 0; + + /* We may modify CHANGES, so make a copy in SCRATCH_POOL. */ + changes = svn_rangelist_dup(changes, scratch_pool); + + while (i < rangelist->nelts && j < changes->nelts) + { + svn_merge_range_t *range = + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_merge_range_t *change = + APR_ARRAY_IDX(changes, j, svn_merge_range_t *); + int res = svn_sort_compare_ranges(&range, &change); + + if (res == 0) + { + /* Only when merging two non-inheritable ranges is the result also + non-inheritable. In all other cases ensure an inheritiable + result. */ + if (range->inheritable || change->inheritable) + range->inheritable = TRUE; + i++; + j++; + } + else if (res < 0) /* CHANGE is younger than RANGE */ + { + if (range->end < change->start) + { + /* RANGE is older than CHANGE and the two do not + adjoin or overlap */ + i++; + } + else if (range->end == change->start) + { + /* RANGE and CHANGE adjoin */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so + RANGE expands to absord CHANGE. */ + range->end = change->end; + adjust_remaining_ranges(rangelist, &i, result_pool); + j++; + } + else + { + /* RANGE and CHANGE adjoin, but have different + inheritability. Since RANGE is older, just + move on to the next RANGE. */ + i++; + } + } + else + { + /* RANGE and CHANGE overlap, but how? */ + if ((range->inheritable == change->inheritable) + || range->inheritable) + { + /* If CHANGE is a proper subset of RANGE, it absorbs RANGE + with no adjustment otherwise only the intersection is + absorbed and CHANGE is truncated. */ + if (range->end >= change->end) + j++; + else + change->start = range->end; + } + else + { + /* RANGE is non-inheritable and CHANGE is inheritable. */ + if (range->start < change->start) + { + /* CHANGE absorbs intersection with RANGE and RANGE + is truncated. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->end = change->start; + range->start = change->start; + svn_sort__array_insert(&range_copy, rangelist, i++); + } + else + { + /* CHANGE and RANGE share the same start rev, but + RANGE is considered older because its end rev + is older. */ + range->inheritable = TRUE; + change->start = range->end; + } + } + } + } + else /* res > 0, CHANGE is older than RANGE */ + { + if (change->end < range->start) + { + /* CHANGE is older than RANGE and the two do not + adjoin or overlap, so insert a copy of CHANGE + into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + svn_sort__array_insert(&change_copy, rangelist, i++); + j++; + } + else if (change->end == range->start) + { + /* RANGE and CHANGE adjoin */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so we + can simply combine the two in place. */ + range->start = change->start; + j++; + } + else + { + /* RANGE and CHANGE have different inheritability so insert + a copy of CHANGE into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + svn_sort__array_insert(&change_copy, rangelist, i); + j++; + } + } + else + { + /* RANGE and CHANGE overlap. */ + if (range->inheritable == change->inheritable) + { + /* RANGE and CHANGE have the same inheritability so we + can simply combine the two in place... */ + range->start = change->start; + if (range->end < change->end) + { + /* ...but if RANGE is expanded ensure that we don't + violate any rangelist invariants. */ + range->end = change->end; + adjust_remaining_ranges(rangelist, &i, result_pool); + } + j++; + } + else if (range->inheritable) + { + if (change->start < range->start) + { + /* RANGE is inheritable so absorbs any part of CHANGE + it overlaps. CHANGE is truncated and the remainder + inserted into RANGELIST. */ + svn_merge_range_t *change_copy = + svn_merge_range_dup(change, result_pool); + change_copy->end = range->start; + change->start = range->start; + svn_sort__array_insert(&change_copy, rangelist, i++); + } + else + { + /* CHANGE and RANGE share the same start rev, but + CHANGE is considered older because CHANGE->END is + older than RANGE->END. */ + j++; + } + } + else + { + /* RANGE is non-inheritable and CHANGE is inheritable. */ + if (change->start < range->start) + { + if (change->end == range->end) + { + /* RANGE is a proper subset of CHANGE and share the + same end revision, so set RANGE equal to CHANGE. */ + range->start = change->start; + range->inheritable = TRUE; + j++; + } + else if (change->end > range->end) + { + /* RANGE is a proper subset of CHANGE and CHANGE has + a younger end revision, so set RANGE equal to its + intersection with CHANGE and truncate CHANGE. */ + range->start = change->start; + range->inheritable = TRUE; + change->start = range->end; + } + else + { + /* CHANGE and RANGE overlap. Set RANGE equal to its + intersection with CHANGE and take the remainder + of RANGE and insert it into RANGELIST. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->start = change->end; + range->start = change->start; + range->end = change->end; + range->inheritable = TRUE; + svn_sort__array_insert(&range_copy, rangelist, ++i); + j++; + } + } + else + { + /* CHANGE and RANGE share the same start rev, but + CHANGE is considered older because its end rev + is older. + + Insert the intersection of RANGE and CHANGE into + RANGELIST and then set RANGE to the non-intersecting + portion of RANGE. */ + svn_merge_range_t *range_copy = + svn_merge_range_dup(range, result_pool); + range_copy->end = change->end; + range_copy->inheritable = TRUE; + range->start = change->end; + svn_sort__array_insert(&range_copy, rangelist, i++); + j++; + } + } + } + } + } + + /* Copy any remaining elements in CHANGES into RANGELIST. */ + for (; j < (changes)->nelts; j++) + { + svn_merge_range_t *change = + APR_ARRAY_IDX(changes, j, svn_merge_range_t *); + svn_merge_range_t *change_copy = svn_merge_range_dup(change, + result_pool); + svn_sort__array_insert(&change_copy, rangelist, rangelist->nelts); + } + + return SVN_NO_ERROR; +} + +/* Return TRUE iff the forward revision ranges FIRST and SECOND overlap and + * (if CONSIDER_INHERITANCE is TRUE) have the same inheritability. */ +static svn_boolean_t +range_intersect(const svn_merge_range_t *first, const svn_merge_range_t *second, + svn_boolean_t consider_inheritance) +{ + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first)); + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second)); + + return (first->start + 1 <= second->end) + && (second->start + 1 <= first->end) + && (!consider_inheritance + || (!(first->inheritable) == !(second->inheritable))); +} + +/* Return TRUE iff the forward revision range FIRST wholly contains the + * forward revision range SECOND and (if CONSIDER_INHERITANCE is TRUE) has + * the same inheritability. */ +static svn_boolean_t +range_contains(const svn_merge_range_t *first, const svn_merge_range_t *second, + svn_boolean_t consider_inheritance) +{ + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(first)); + SVN_ERR_ASSERT_NO_RETURN(IS_VALID_FORWARD_RANGE(second)); + + return (first->start <= second->start) && (second->end <= first->end) + && (!consider_inheritance + || (!(first->inheritable) == !(second->inheritable))); +} + +/* Swap start and end fields of RANGE. */ +static void +range_swap_endpoints(svn_merge_range_t *range) +{ + svn_revnum_t swap = range->start; + range->start = range->end; + range->end = swap; +} + +svn_error_t * +svn_rangelist_reverse(svn_rangelist_t *rangelist, apr_pool_t *pool) +{ + int i; + + svn_sort__array_reverse(rangelist, pool); + + for (i = 0; i < rangelist->nelts; i++) + { + range_swap_endpoints(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *)); + } + + return SVN_NO_ERROR; +} + +void +svn_rangelist__set_inheritance(svn_rangelist_t *rangelist, + svn_boolean_t inheritable) +{ + if (rangelist) + { + int i; + svn_merge_range_t *range; + + for (i = 0; i < rangelist->nelts; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + range->inheritable = inheritable; + } + } + return; +} + +void +svn_mergeinfo__set_inheritance(svn_mergeinfo_t mergeinfo, + svn_boolean_t inheritable, + apr_pool_t *scratch_pool) +{ + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist) + svn_rangelist__set_inheritance(rangelist, inheritable); + } + } + return; +} + +/* If DO_REMOVE is true, then remove any overlapping ranges described by + RANGELIST1 from RANGELIST2 and place the results in *OUTPUT. When + DO_REMOVE is true, RANGELIST1 is effectively the "eraser" and RANGELIST2 + the "whiteboard". + + If DO_REMOVE is false, then capture the intersection between RANGELIST1 + and RANGELIST2 and place the results in *OUTPUT. The ordering of + RANGELIST1 and RANGELIST2 doesn't matter when DO_REMOVE is false. + + If CONSIDER_INHERITANCE is true, then take the inheritance of the + ranges in RANGELIST1 and RANGELIST2 into account when comparing them + for intersection, see the doc string for svn_rangelist_intersect(). + + If CONSIDER_INHERITANCE is false, then ranges with differing inheritance + may intersect, but the resulting intersection is non-inheritable only + if both ranges were non-inheritable, e.g.: + + RANGELIST1 RANGELIST2 CONSIDER DO_REMOVE *OUTPUT + INHERITANCE + ---------- ------ ----------- --------- ------- + + 90-420* 1-100 TRUE FALSE Empty Rangelist + 90-420 1-100* TRUE FALSE Empty Rangelist + 90-420 1-100 TRUE FALSE 90-100 + 90-420* 1-100* TRUE FALSE 90-100* + + 90-420* 1-100 FALSE FALSE 90-100 + 90-420 1-100* FALSE FALSE 90-100 + 90-420 1-100 FALSE FALSE 90-100 + 90-420* 1-100* FALSE FALSE 90-100* + + Allocate the contents of *OUTPUT in POOL. */ +static svn_error_t * +rangelist_intersect_or_remove(svn_rangelist_t **output, + const svn_rangelist_t *rangelist1, + const svn_rangelist_t *rangelist2, + svn_boolean_t do_remove, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + int i1, i2, lasti2; + svn_merge_range_t working_elt2; + + *output = apr_array_make(pool, 1, sizeof(svn_merge_range_t *)); + + i1 = 0; + i2 = 0; + lasti2 = -1; /* Initialized to a value that "i2" will never be. */ + + while (i1 < rangelist1->nelts && i2 < rangelist2->nelts) + { + svn_merge_range_t *elt1, *elt2; + + elt1 = APR_ARRAY_IDX(rangelist1, i1, svn_merge_range_t *); + + /* Instead of making a copy of the entire array of rangelist2 + elements, we just keep a copy of the current rangelist2 element + that needs to be used, and modify our copy if necessary. */ + if (i2 != lasti2) + { + working_elt2 = + *(APR_ARRAY_IDX(rangelist2, i2, svn_merge_range_t *)); + lasti2 = i2; + } + + elt2 = &working_elt2; + + /* If the rangelist2 range is contained completely in the + rangelist1, we increment the rangelist2. + If the ranges intersect, and match exactly, we increment both + rangelist1 and rangelist2. + Otherwise, we have to generate a range for the left part of + the removal of rangelist1 from rangelist2, and possibly change + the rangelist2 to the remaining portion of the right part of + the removal, to test against. */ + if (range_contains(elt1, elt2, consider_inheritance)) + { + if (!do_remove) + { + svn_merge_range_t tmp_range; + tmp_range.start = elt2->start; + tmp_range.end = elt2->end; + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + SVN_ERR(combine_with_lastrange(&tmp_range, *output, + consider_inheritance, + pool)); + } + + i2++; + + if (elt2->start == elt1->start && elt2->end == elt1->end) + i1++; + } + else if (range_intersect(elt1, elt2, consider_inheritance)) + { + if (elt2->start < elt1->start) + { + /* The rangelist2 range starts before the rangelist1 range. */ + svn_merge_range_t tmp_range; + if (do_remove) + { + /* Retain the range that falls before the rangelist1 + start. */ + tmp_range.start = elt2->start; + tmp_range.end = elt1->start; + tmp_range.inheritable = elt2->inheritable; + } + else + { + /* Retain the range that falls between the rangelist1 + start and rangelist2 end. */ + tmp_range.start = elt1->start; + tmp_range.end = MIN(elt2->end, elt1->end); + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + } + + SVN_ERR(combine_with_lastrange(&tmp_range, + *output, consider_inheritance, + pool)); + } + + /* Set up the rest of the rangelist2 range for further + processing. */ + if (elt2->end > elt1->end) + { + /* The rangelist2 range ends after the rangelist1 range. */ + if (!do_remove) + { + /* Partial overlap. */ + svn_merge_range_t tmp_range; + tmp_range.start = MAX(elt2->start, elt1->start); + tmp_range.end = elt1->end; + /* The intersection of two ranges is non-inheritable only + if both ranges are non-inheritable. */ + tmp_range.inheritable = + (elt2->inheritable || elt1->inheritable); + SVN_ERR(combine_with_lastrange(&tmp_range, + *output, + consider_inheritance, + pool)); + } + + working_elt2.start = elt1->end; + working_elt2.end = elt2->end; + } + else + i2++; + } + else /* ranges don't intersect */ + { + /* See which side of the rangelist2 the rangelist1 is on. If it + is on the left side, we need to move the rangelist1. + + If it is on past the rangelist2 on the right side, we + need to output the rangelist2 and increment the + rangelist2. */ + if (svn_sort_compare_ranges(&elt1, &elt2) < 0) + i1++; + else + { + svn_merge_range_t *lastrange; + + if ((*output)->nelts > 0) + lastrange = APR_ARRAY_IDX(*output, (*output)->nelts - 1, + svn_merge_range_t *); + else + lastrange = NULL; + + if (do_remove && !(lastrange && + combine_ranges(lastrange, lastrange, elt2, + consider_inheritance))) + { + lastrange = svn_merge_range_dup(elt2, pool); + APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange; + } + i2++; + } + } + } + + if (do_remove) + { + /* Copy the current rangelist2 element if we didn't hit the end + of the rangelist2, and we still had it around. This element + may have been touched, so we can't just walk the rangelist2 + array, we have to use our copy. This case only happens when + we ran out of rangelist1 before rangelist2, *and* we had changed + the rangelist2 element. */ + if (i2 == lasti2 && i2 < rangelist2->nelts) + { + SVN_ERR(combine_with_lastrange(&working_elt2, *output, + consider_inheritance, pool)); + i2++; + } + + /* Copy any other remaining untouched rangelist2 elements. */ + for (; i2 < rangelist2->nelts; i2++) + { + svn_merge_range_t *elt = APR_ARRAY_IDX(rangelist2, i2, + svn_merge_range_t *); + + SVN_ERR(combine_with_lastrange(elt, *output, + consider_inheritance, pool)); + } + } + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_rangelist_intersect(svn_rangelist_t **output, + const svn_rangelist_t *rangelist1, + const svn_rangelist_t *rangelist2, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE, + consider_inheritance, pool); +} + +svn_error_t * +svn_rangelist_remove(svn_rangelist_t **output, + const svn_rangelist_t *eraser, + const svn_rangelist_t *whiteboard, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE, + consider_inheritance, pool); +} + +svn_error_t * +svn_rangelist_diff(svn_rangelist_t **deleted, svn_rangelist_t **added, + const svn_rangelist_t *from, const svn_rangelist_t *to, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + /* The following diagrams illustrate some common range delta scenarios: + + (from) deleted + r0 <===========(=========)============[=========]===========> rHEAD + [to] added + + (from) deleted deleted + r0 <===========(=========[============]=========)===========> rHEAD + [to] + + (from) deleted + r0 <===========(=========[============)=========]===========> rHEAD + [to] added + + (from) deleted + r0 <===========[=========(============]=========)===========> rHEAD + [to] added + + (from) + r0 <===========[=========(============)=========]===========> rHEAD + [to] added added + + (from) d d d + r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD + [to] a a a a a a a + */ + + /* The items that are present in from, but not in to, must have been + deleted. */ + SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance, + pool)); + /* The items that are present in to, but not in from, must have been + added. */ + return svn_rangelist_remove(added, from, to, consider_inheritance, pool); +} + +struct mergeinfo_diff_baton +{ + svn_mergeinfo_t from; + svn_mergeinfo_t to; + svn_mergeinfo_t deleted; + svn_mergeinfo_t added; + svn_boolean_t consider_inheritance; + apr_pool_t *pool; +}; + +/* This implements the 'svn_hash_diff_func_t' interface. + BATON is of type 'struct mergeinfo_diff_baton *'. +*/ +static svn_error_t * +mergeinfo_hash_diff_cb(const void *key, apr_ssize_t klen, + enum svn_hash_diff_key_status status, + void *baton) +{ + /* hash_a is FROM mergeinfo, + hash_b is TO mergeinfo. */ + struct mergeinfo_diff_baton *cb = baton; + svn_rangelist_t *from_rangelist, *to_rangelist; + const char *path = key; + if (status == svn_hash_diff_key_both) + { + /* Record any deltas (additions or deletions). */ + svn_rangelist_t *deleted_rangelist, *added_rangelist; + from_rangelist = apr_hash_get(cb->from, path, klen); + to_rangelist = apr_hash_get(cb->to, path, klen); + SVN_ERR(svn_rangelist_diff(&deleted_rangelist, &added_rangelist, + from_rangelist, to_rangelist, + cb->consider_inheritance, cb->pool)); + if (cb->deleted && deleted_rangelist->nelts > 0) + apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), + klen, deleted_rangelist); + if (cb->added && added_rangelist->nelts > 0) + apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), + klen, added_rangelist); + } + else if ((status == svn_hash_diff_key_a) && cb->deleted) + { + from_rangelist = apr_hash_get(cb->from, path, klen); + apr_hash_set(cb->deleted, apr_pstrmemdup(cb->pool, path, klen), klen, + svn_rangelist_dup(from_rangelist, cb->pool)); + } + else if ((status == svn_hash_diff_key_b) && cb->added) + { + to_rangelist = apr_hash_get(cb->to, path, klen); + apr_hash_set(cb->added, apr_pstrmemdup(cb->pool, path, klen), klen, + svn_rangelist_dup(to_rangelist, cb->pool)); + } + return SVN_NO_ERROR; +} + +/* Record deletions and additions of entire range lists (by path + presence), and delegate to svn_rangelist_diff() for delta + calculations on a specific path. */ +static svn_error_t * +walk_mergeinfo_hash_for_diff(svn_mergeinfo_t from, svn_mergeinfo_t to, + svn_mergeinfo_t deleted, svn_mergeinfo_t added, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct mergeinfo_diff_baton mdb; + mdb.from = from; + mdb.to = to; + mdb.deleted = deleted; + mdb.added = added; + mdb.consider_inheritance = consider_inheritance; + mdb.pool = result_pool; + + return svn_hash_diff(from, to, mergeinfo_hash_diff_cb, &mdb, scratch_pool); +} + +svn_error_t * +svn_mergeinfo_diff2(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added, + svn_mergeinfo_t from, svn_mergeinfo_t to, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (from && to == NULL) + { + *deleted = svn_mergeinfo_dup(from, result_pool); + *added = svn_hash__make(result_pool); + } + else if (from == NULL && to) + { + *deleted = svn_hash__make(result_pool); + *added = svn_mergeinfo_dup(to, result_pool); + } + else + { + *deleted = svn_hash__make(result_pool); + *added = svn_hash__make(result_pool); + + if (from && to) + { + SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added, + consider_inheritance, + result_pool, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__equals(svn_boolean_t *is_equal, + svn_mergeinfo_t info1, + svn_mergeinfo_t info2, + svn_boolean_t consider_inheritance, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + *is_equal = FALSE; + + /* special cases: at least one side has no merge info */ + if (info1 == NULL && info2 == NULL) + { + *is_equal = TRUE; + return SVN_NO_ERROR; + } + + if (info1 == NULL || info2 == NULL) + return SVN_NO_ERROR; + + /* trivial case: different number of paths -> unequal */ + if (apr_hash_count(info1) != apr_hash_count(info2)) + return SVN_NO_ERROR; + + /* compare range lists for all paths */ + for (hi = apr_hash_first(pool, info1); hi; hi = apr_hash_next(hi)) + { + const char *key; + apr_ssize_t key_length; + svn_rangelist_t *lhs, *rhs; + int i; + svn_rangelist_t *deleted, *added; + + /* get both path lists */ + apr_hash_this(hi, (const void**)&key, &key_length, (void **)&lhs); + rhs = apr_hash_get(info2, key, key_length); + + /* missing on one side? */ + if (rhs == NULL) + return SVN_NO_ERROR; + + /* quick compare: the range lists will often be a perfect match */ + if (lhs->nelts == rhs->nelts) + { + for (i = 0; i < lhs->nelts; ++i) + { + svn_merge_range_t *lrange + = APR_ARRAY_IDX(lhs, i, svn_merge_range_t *); + svn_merge_range_t *rrange + = APR_ARRAY_IDX(rhs, i, svn_merge_range_t *); + + /* range mismatch? -> needs detailed comparison */ + if ( lrange->start != rrange->start + || lrange->end != rrange->end) + break; + + /* inheritance mismatch? -> merge info differs */ + if ( consider_inheritance + && lrange->inheritable != rrange->inheritable) + return SVN_NO_ERROR; + } + + /* all ranges found to match -> next path */ + if (i == lhs->nelts) + continue; + } + + /* range lists differ but there are many ways to sort and aggregate + revisions into ranges. Do a full diff on them. */ + SVN_ERR(svn_rangelist_diff(&deleted, &added, lhs, rhs, + consider_inheritance, pool)); + if (deleted->nelts || added->nelts) + return SVN_NO_ERROR; + } + + /* no mismatch found */ + *is_equal = TRUE; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_merge2(svn_mergeinfo_t mergeinfo, + svn_mergeinfo_t changes, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + if (!apr_hash_count(changes)) + return SVN_NO_ERROR; + + iterpool = svn_pool_create(scratch_pool); + for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi)) + { + const char *key; + apr_ssize_t klen; + svn_rangelist_t *to_insert; + svn_rangelist_t *target; + + /* get ranges to insert and the target ranges list of that insertion */ + apr_hash_this(hi, (const void**)&key, &klen, (void*)&to_insert); + target = apr_hash_get(mergeinfo, key, klen); + + /* if range list exists, just expand on it. + * Otherwise, add new hash entry. */ + if (target) + { + SVN_ERR(svn_rangelist_merge2(target, to_insert, result_pool, + iterpool)); + svn_pool_clear(iterpool); + } + else + apr_hash_set(mergeinfo, key, klen, to_insert); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_catalog_merge(svn_mergeinfo_catalog_t mergeinfo_cat, + svn_mergeinfo_catalog_t changes_cat, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i = 0; + int j = 0; + apr_array_header_t *sorted_cat = + svn_sort__hash(mergeinfo_cat, svn_sort_compare_items_as_paths, + scratch_pool); + apr_array_header_t *sorted_changes = + svn_sort__hash(changes_cat, svn_sort_compare_items_as_paths, + scratch_pool); + + while (i < sorted_cat->nelts && j < sorted_changes->nelts) + { + svn_sort__item_t cat_elt, change_elt; + int res; + + cat_elt = APR_ARRAY_IDX(sorted_cat, i, svn_sort__item_t); + change_elt = APR_ARRAY_IDX(sorted_changes, j, svn_sort__item_t); + res = svn_sort_compare_items_as_paths(&cat_elt, &change_elt); + + if (res == 0) /* Both catalogs have mergeinfo for a given path. */ + { + svn_mergeinfo_t mergeinfo = cat_elt.value; + svn_mergeinfo_t changes_mergeinfo = change_elt.value; + + SVN_ERR(svn_mergeinfo_merge2(mergeinfo, changes_mergeinfo, + result_pool, scratch_pool)); + apr_hash_set(mergeinfo_cat, cat_elt.key, cat_elt.klen, mergeinfo); + i++; + j++; + } + else if (res < 0) /* Only MERGEINFO_CAT has mergeinfo for this path. */ + { + i++; + } + else /* Only CHANGES_CAT has mergeinfo for this path. */ + { + apr_hash_set(mergeinfo_cat, + apr_pstrdup(result_pool, change_elt.key), + change_elt.klen, + svn_mergeinfo_dup(change_elt.value, result_pool)); + j++; + } + } + + /* Copy back any remaining elements from the CHANGES_CAT catalog. */ + for (; j < sorted_changes->nelts; j++) + { + svn_sort__item_t elt = APR_ARRAY_IDX(sorted_changes, j, + svn_sort__item_t); + apr_hash_set(mergeinfo_cat, + apr_pstrdup(result_pool, elt.key), + elt.klen, + svn_mergeinfo_dup(elt.value, result_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_intersect2(svn_mergeinfo_t *mergeinfo, + svn_mergeinfo_t mergeinfo1, + svn_mergeinfo_t mergeinfo2, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + *mergeinfo = apr_hash_make(result_pool); + iterpool = svn_pool_create(scratch_pool); + + /* ### TODO(reint): Do we care about the case when a path in one + ### mergeinfo hash has inheritable mergeinfo, and in the other + ### has non-inhertiable mergeinfo? It seems like that path + ### itself should really be an intersection, while child paths + ### should not be... */ + for (hi = apr_hash_first(scratch_pool, mergeinfo1); + hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist1 = svn__apr_hash_index_val(hi); + svn_rangelist_t *rangelist2; + + svn_pool_clear(iterpool); + rangelist2 = svn_hash_gets(mergeinfo2, path); + if (rangelist2) + { + SVN_ERR(svn_rangelist_intersect(&rangelist2, rangelist1, rangelist2, + consider_inheritance, iterpool)); + if (rangelist2->nelts > 0) + svn_hash_sets(*mergeinfo, apr_pstrdup(result_pool, path), + svn_rangelist_dup(rangelist2, result_pool)); + } + } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_remove2(svn_mergeinfo_t *mergeinfo, + svn_mergeinfo_t eraser, + svn_mergeinfo_t whiteboard, + svn_boolean_t consider_inheritance, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *mergeinfo = apr_hash_make(result_pool); + return walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL, + consider_inheritance, result_pool, + scratch_pool); +} + +svn_error_t * +svn_rangelist_to_string(svn_string_t **output, + const svn_rangelist_t *rangelist, + apr_pool_t *pool) +{ + svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); + + if (rangelist->nelts > 0) + { + int i; + svn_merge_range_t *range; + + /* Handle the elements that need commas at the end. */ + for (i = 0; i < rangelist->nelts - 1; i++) + { + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_stringbuf_appendcstr(buf, range_to_string(range, pool)); + svn_stringbuf_appendcstr(buf, ","); + } + + /* Now handle the last element, which needs no comma. */ + range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + svn_stringbuf_appendcstr(buf, range_to_string(range, pool)); + } + + *output = svn_stringbuf__morph_into_string(buf); + + return SVN_NO_ERROR; +} + +/* Converts a mergeinfo INPUT to an unparsed mergeinfo in OUTPUT. If PREFIX + is not NULL then prepend PREFIX to each line in OUTPUT. If INPUT contains + no elements, return the empty string. If INPUT contains any merge source + path keys that are relative then convert these to absolute paths in + *OUTPUT. + */ +static svn_error_t * +mergeinfo_to_stringbuf(svn_stringbuf_t **output, + svn_mergeinfo_t input, + const char *prefix, + apr_pool_t *pool) +{ + *output = svn_stringbuf_create_empty(pool); + + if (apr_hash_count(input) > 0) + { + apr_array_header_t *sorted = + svn_sort__hash(input, svn_sort_compare_items_as_paths, pool); + int i; + + for (i = 0; i < sorted->nelts; i++) + { + svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t); + svn_string_t *revlist; + + SVN_ERR(svn_rangelist_to_string(&revlist, elt.value, pool)); + svn_stringbuf_appendcstr( + *output, + apr_psprintf(pool, "%s%s%s:%s", + prefix ? prefix : "", + *((const char *) elt.key) == '/' ? "" : "/", + (const char *) elt.key, + revlist->data)); + if (i < sorted->nelts - 1) + svn_stringbuf_appendcstr(*output, "\n"); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_to_string(svn_string_t **output, svn_mergeinfo_t input, + apr_pool_t *pool) +{ + svn_stringbuf_t *mergeinfo_buf; + + SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_buf, input, NULL, pool)); + *output = svn_stringbuf__morph_into_string(mergeinfo_buf); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo_sort(svn_mergeinfo_t input, apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi)) + { + apr_array_header_t *rl = svn__apr_hash_index_val(hi); + + qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges); + } + return SVN_NO_ERROR; +} + +svn_mergeinfo_catalog_t +svn_mergeinfo_catalog_dup(svn_mergeinfo_catalog_t mergeinfo_catalog, + apr_pool_t *pool) +{ + svn_mergeinfo_t new_mergeinfo_catalog = apr_hash_make(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo_catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *key = svn__apr_hash_index_key(hi); + svn_mergeinfo_t val = svn__apr_hash_index_val(hi); + + svn_hash_sets(new_mergeinfo_catalog, apr_pstrdup(pool, key), + svn_mergeinfo_dup(val, pool)); + } + + return new_mergeinfo_catalog; +} + +svn_mergeinfo_t +svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool) +{ + svn_mergeinfo_t new_mergeinfo = svn_hash__make(pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + apr_ssize_t pathlen = svn__apr_hash_index_klen(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen, + svn_rangelist_dup(rangelist, pool)); + } + + return new_mergeinfo; +} + +svn_error_t * +svn_mergeinfo_inheritable2(svn_mergeinfo_t *output, + svn_mergeinfo_t mergeinfo, + const char *path, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + svn_mergeinfo_t inheritable_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); + apr_ssize_t keylen = svn__apr_hash_index_klen(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + svn_rangelist_t *inheritable_rangelist; + + if (!path || svn_path_compare_paths(path, key) == 0) + SVN_ERR(svn_rangelist_inheritable2(&inheritable_rangelist, rangelist, + start, end, inheritable, + result_pool, scratch_pool)); + else + inheritable_rangelist = svn_rangelist_dup(rangelist, result_pool); + + /* Only add this rangelist if some ranges remain. A rangelist with + a path mapped to an empty rangelist is not syntactically valid */ + if (inheritable_rangelist->nelts) + apr_hash_set(inheritable_mergeinfo, + apr_pstrmemdup(result_pool, key, keylen), keylen, + inheritable_rangelist); + } + *output = inheritable_mergeinfo; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_rangelist_inheritable2(svn_rangelist_t **inheritable_rangelist, + const svn_rangelist_t *rangelist, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *inheritable_rangelist = apr_array_make(result_pool, 1, + sizeof(svn_merge_range_t *)); + if (rangelist->nelts) + { + if (!SVN_IS_VALID_REVNUM(start) + || !SVN_IS_VALID_REVNUM(end) + || end < start) + { + int i; + /* We want all non-inheritable ranges removed. */ + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + if (range->inheritable == inheritable) + { + svn_merge_range_t *inheritable_range = + apr_palloc(result_pool, sizeof(*inheritable_range)); + inheritable_range->start = range->start; + inheritable_range->end = range->end; + inheritable_range->inheritable = TRUE; + APR_ARRAY_PUSH(*inheritable_rangelist, + svn_merge_range_t *) = range; + } + } + } + else + { + /* We want only the non-inheritable ranges bound by START + and END removed. */ + svn_rangelist_t *ranges_inheritable = + svn_rangelist__initialize(start, end, inheritable, scratch_pool); + + if (rangelist->nelts) + SVN_ERR(svn_rangelist_remove(inheritable_rangelist, + ranges_inheritable, + rangelist, + TRUE, + result_pool)); + } + } + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + svn_boolean_t removed_some_ranges = FALSE; + + if (mergeinfo) + { + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts == 0) + { + svn_hash_sets(mergeinfo, path, NULL); + removed_some_ranges = TRUE; + } + } + } + return removed_some_ranges; +} + +svn_error_t * +svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog, + svn_mergeinfo_catalog_t in_catalog, + const char *prefix_path, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(prefix_path[0] == '/'); + + *out_catalog = apr_hash_make(pool); + + for (hi = apr_hash_first(pool, in_catalog); hi; hi = apr_hash_next(hi)) + { + const char *original_path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t value = svn__apr_hash_index_val(hi); + const char *new_path; + + new_path = svn_fspath__skip_ancestor(prefix_path, original_path); + SVN_ERR_ASSERT(new_path); + + svn_hash_sets(*out_catalog, new_path, value); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__add_prefix_to_catalog(svn_mergeinfo_catalog_t *out_catalog, + svn_mergeinfo_catalog_t in_catalog, + const char *prefix_path, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + *out_catalog = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, in_catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *original_path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t value = svn__apr_hash_index_val(hi); + + if (original_path[0] == '/') + original_path++; + + svn_hash_sets(*out_catalog, + svn_dirent_join(prefix_path, original_path, result_pool), + value); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__add_suffix_to_mergeinfo(svn_mergeinfo_t *out_mergeinfo, + svn_mergeinfo_t mergeinfo, + const char *suffix_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + SVN_ERR_ASSERT(suffix_relpath && svn_relpath_is_canonical(suffix_relpath)); + + *out_mergeinfo = apr_hash_make(result_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *fspath = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + svn_hash_sets(*out_mergeinfo, + svn_fspath__join(fspath, suffix_relpath, result_pool), + rangelist); + } + + return SVN_NO_ERROR; +} + +svn_rangelist_t * +svn_rangelist_dup(const svn_rangelist_t *rangelist, apr_pool_t *pool) +{ + svn_rangelist_t *new_rl = apr_array_make(pool, rangelist->nelts, + sizeof(svn_merge_range_t *)); + + /* allocate target range buffer with a single operation */ + svn_merge_range_t *copy = apr_palloc(pool, sizeof(*copy) * rangelist->nelts); + int i; + + /* fill it iteratively and link it into the range list */ + for (i = 0; i < rangelist->nelts; i++) + { + memcpy(copy + i, + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *), + sizeof(*copy)); + APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) = copy + i; + } + + return new_rl; +} + +svn_merge_range_t * +svn_merge_range_dup(const svn_merge_range_t *range, apr_pool_t *pool) +{ + svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range)); + memcpy(new_range, range, sizeof(*new_range)); + return new_range; +} + +svn_boolean_t +svn_merge_range_contains_rev(const svn_merge_range_t *range, svn_revnum_t rev) +{ + assert(SVN_IS_VALID_REVNUM(range->start)); + assert(SVN_IS_VALID_REVNUM(range->end)); + assert(range->start != range->end); + + if (range->start < range->end) + return rev > range->start && rev <= range->end; + else + return rev > range->end && rev <= range->start; +} + +svn_error_t * +svn_mergeinfo__catalog_to_formatted_string(svn_string_t **output, + svn_mergeinfo_catalog_t catalog, + const char *key_prefix, + const char *val_prefix, + apr_pool_t *pool) +{ + svn_stringbuf_t *output_buf = NULL; + + if (catalog && apr_hash_count(catalog)) + { + int i; + apr_array_header_t *sorted_catalog = + svn_sort__hash(catalog, svn_sort_compare_items_as_paths, pool); + + output_buf = svn_stringbuf_create_empty(pool); + for (i = 0; i < sorted_catalog->nelts; i++) + { + svn_sort__item_t elt = + APR_ARRAY_IDX(sorted_catalog, i, svn_sort__item_t); + const char *path1; + svn_mergeinfo_t mergeinfo; + svn_stringbuf_t *mergeinfo_output_buf; + + path1 = elt.key; + mergeinfo = elt.value; + if (key_prefix) + svn_stringbuf_appendcstr(output_buf, key_prefix); + svn_stringbuf_appendcstr(output_buf, path1); + svn_stringbuf_appendcstr(output_buf, "\n"); + SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_output_buf, mergeinfo, + val_prefix ? val_prefix : "", pool)); + svn_stringbuf_appendstr(output_buf, mergeinfo_output_buf); + svn_stringbuf_appendcstr(output_buf, "\n"); + } + } +#if SVN_DEBUG + else if (!catalog) + { + output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool); + svn_stringbuf_appendcstr(output_buf, _("NULL mergeinfo catalog\n")); + } + else if (apr_hash_count(catalog) == 0) + { + output_buf = svn_stringbuf_create(key_prefix ? key_prefix : "", pool); + svn_stringbuf_appendcstr(output_buf, _("empty mergeinfo catalog\n")); + } +#endif + + /* If we have an output_buf, convert it to an svn_string_t; + otherwise, return a new string containing only a newline + character. */ + if (output_buf) + *output = svn_stringbuf__morph_into_string(output_buf); + else + *output = svn_string_create("\n", pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__get_range_endpoints(svn_revnum_t *youngest_rev, + svn_revnum_t *oldest_rev, + svn_mergeinfo_t mergeinfo, + apr_pool_t *pool) +{ + *youngest_rev = *oldest_rev = SVN_INVALID_REVNUM; + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, + rangelist->nelts - 1, + svn_merge_range_t *); + if (!SVN_IS_VALID_REVNUM(*youngest_rev) + || (range->end > *youngest_rev)) + *youngest_rev = range->end; + + range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); + if (!SVN_IS_VALID_REVNUM(*oldest_rev) + || (range->start < *oldest_rev)) + *oldest_rev = range->start; + } + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__filter_catalog_by_ranges(svn_mergeinfo_catalog_t *filtered_cat, + svn_mergeinfo_catalog_t catalog, + svn_revnum_t youngest_rev, + svn_revnum_t oldest_rev, + svn_boolean_t include_range, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + *filtered_cat = apr_hash_make(result_pool); + for (hi = apr_hash_first(scratch_pool, catalog); + hi; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_mergeinfo_t mergeinfo = svn__apr_hash_index_val(hi); + svn_mergeinfo_t filtered_mergeinfo; + + SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(&filtered_mergeinfo, + mergeinfo, + youngest_rev, + oldest_rev, + include_range, + result_pool, + scratch_pool)); + if (apr_hash_count(filtered_mergeinfo)) + svn_hash_sets(*filtered_cat, + apr_pstrdup(result_pool, path), filtered_mergeinfo); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__filter_mergeinfo_by_ranges(svn_mergeinfo_t *filtered_mergeinfo, + svn_mergeinfo_t mergeinfo, + svn_revnum_t youngest_rev, + svn_revnum_t oldest_rev, + svn_boolean_t include_range, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(youngest_rev)); + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(oldest_rev)); + SVN_ERR_ASSERT(oldest_rev < youngest_rev); + + *filtered_mergeinfo = apr_hash_make(result_pool); + + if (mergeinfo) + { + apr_hash_index_t *hi; + svn_rangelist_t *filter_rangelist = + svn_rangelist__initialize(oldest_rev, youngest_rev, TRUE, + scratch_pool); + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + + if (rangelist->nelts) + { + svn_rangelist_t *new_rangelist; + + SVN_ERR(rangelist_intersect_or_remove( + &new_rangelist, filter_rangelist, rangelist, + ! include_range, FALSE, result_pool)); + + if (new_rangelist->nelts) + svn_hash_sets(*filtered_mergeinfo, + apr_pstrdup(result_pool, path), new_rangelist); + } + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_mergeinfo__adjust_mergeinfo_rangelists(svn_mergeinfo_t *adjusted_mergeinfo, + svn_mergeinfo_t mergeinfo, + svn_revnum_t offset, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + *adjusted_mergeinfo = apr_hash_make(result_pool); + + if (mergeinfo) + { + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + int i; + const char *path = svn__apr_hash_index_key(hi); + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + svn_rangelist_t *adjusted_rangelist = + apr_array_make(result_pool, rangelist->nelts, + sizeof(svn_merge_range_t *)); + + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = + APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *); + + if (range->start + offset > 0 && range->end + offset > 0) + { + if (range->start + offset < 0) + range->start = 0; + else + range->start = range->start + offset; + + if (range->end + offset < 0) + range->end = 0; + else + range->end = range->end + offset; + APR_ARRAY_PUSH(adjusted_rangelist, svn_merge_range_t *) = + range; + } + } + + if (adjusted_rangelist->nelts) + svn_hash_sets(*adjusted_mergeinfo, apr_pstrdup(result_pool, path), + adjusted_rangelist); + } + } + return SVN_NO_ERROR; +} + +svn_boolean_t +svn_mergeinfo__is_noninheritable(svn_mergeinfo_t mergeinfo, + apr_pool_t *scratch_pool) +{ + if (mergeinfo) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, mergeinfo); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *rangelist = svn__apr_hash_index_val(hi); + int i; + + for (i = 0; i < rangelist->nelts; i++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, + svn_merge_range_t *); + if (!range->inheritable) + return TRUE; + } + } + } + return FALSE; +} + +svn_rangelist_t * +svn_rangelist__initialize(svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *result_pool) +{ + svn_rangelist_t *rangelist = + apr_array_make(result_pool, 1, sizeof(svn_merge_range_t *)); + svn_merge_range_t *range = apr_pcalloc(result_pool, sizeof(*range)); + + range->start = start; + range->end = end; + range->inheritable = inheritable; + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range; + return rangelist; +} + +svn_error_t * +svn_mergeinfo__mergeinfo_from_segments(svn_mergeinfo_t *mergeinfo_p, + const apr_array_header_t *segments, + apr_pool_t *pool) +{ + svn_mergeinfo_t mergeinfo = apr_hash_make(pool); + int i; + + /* Translate location segments into merge sources and ranges. */ + for (i = 0; i < segments->nelts; i++) + { + svn_location_segment_t *segment = + APR_ARRAY_IDX(segments, i, svn_location_segment_t *); + svn_rangelist_t *path_ranges; + svn_merge_range_t *range; + const char *source_path; + + /* No path segment? Skip it. */ + if (! segment->path) + continue; + + /* Prepend a leading slash to our path. */ + source_path = apr_pstrcat(pool, "/", segment->path, (char *)NULL); + + /* See if we already stored ranges for this path. If not, make + a new list. */ + path_ranges = svn_hash_gets(mergeinfo, source_path); + if (! path_ranges) + path_ranges = apr_array_make(pool, 1, sizeof(range)); + + /* A svn_location_segment_t may have legitimately describe only + revision 0, but there is no corresponding representation for + this in a svn_merge_range_t. */ + if (segment->range_start == 0 && segment->range_end == 0) + continue; + + /* Build a merge range, push it onto the list of ranges, and for + good measure, (re)store it in the hash. */ + range = apr_pcalloc(pool, sizeof(*range)); + range->start = MAX(segment->range_start - 1, 0); + range->end = segment->range_end; + range->inheritable = TRUE; + APR_ARRAY_PUSH(path_ranges, svn_merge_range_t *) = range; + svn_hash_sets(mergeinfo, source_path, path_ranges); + } + + *mergeinfo_p = mergeinfo; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_rangelist__merge_many(svn_rangelist_t *merged_rangelist, + svn_mergeinfo_t merge_history, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (apr_hash_count(merge_history)) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, merge_history); + hi; + hi = apr_hash_next(hi)) + { + svn_rangelist_t *subtree_rangelist = svn__apr_hash_index_val(hi); + + svn_pool_clear(iterpool); + SVN_ERR(svn_rangelist_merge2(merged_rangelist, subtree_rangelist, + result_pool, iterpool)); + } + svn_pool_destroy(iterpool); + } + return SVN_NO_ERROR; +} + + +const char * +svn_inheritance_to_word(svn_mergeinfo_inheritance_t inherit) +{ + switch (inherit) + { + case svn_mergeinfo_inherited: + return "inherited"; + case svn_mergeinfo_nearest_ancestor: + return "nearest-ancestor"; + default: + return "explicit"; + } +} + +svn_mergeinfo_inheritance_t +svn_inheritance_from_word(const char *word) +{ + if (strcmp(word, "inherited") == 0) + return svn_mergeinfo_inherited; + if (strcmp(word, "nearest-ancestor") == 0) + return svn_mergeinfo_nearest_ancestor; + return svn_mergeinfo_explicit; +} |