Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / libsvn_subr / mergeinfo.c
blob5e98d1f3d9ab2eeeec1e29ebbf76b6a1bc6cec90
1 /*
2 * mergeinfo.c: Mergeinfo parsing and handling
4 * ====================================================================
5 * Copyright (c) 2006-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
18 #include <assert.h>
19 #include <ctype.h>
21 #include "svn_path.h"
22 #include "svn_types.h"
23 #include "svn_ctype.h"
24 #include "svn_pools.h"
25 #include "svn_sorts.h"
26 #include "svn_error.h"
27 #include "svn_error_codes.h"
28 #include "svn_string.h"
29 #include "svn_mergeinfo.h"
30 #include "private/svn_mergeinfo_private.h"
31 #include "svn_private_config.h"
33 /* Attempt to combine two adjacent or overlapping ranges, IN1 and IN2, and put
34 the result in OUTPUT. Return whether they could be combined.
36 CONSIDER_INHERITANCE determines how to account for the inheritability
37 of IN1 and IN2 when trying to combine ranges. If ranges with different
38 inheritability are combined (CONSIDER_INHERITANCE must be FALSE for this
39 to happen) the result is inheritable. If both ranges are inheritable the
40 result is inheritable. Only and if both ranges are non-inheritable is
41 the result is non-inheritable.
43 Range overlapping detection algorithm from
44 http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap
46 static svn_boolean_t
47 combine_ranges(svn_merge_range_t **output, svn_merge_range_t *in1,
48 svn_merge_range_t *in2,
49 svn_boolean_t consider_inheritance)
51 if (in1->start <= in2->end && in2->start <= in1->end)
53 if (!consider_inheritance
54 || (consider_inheritance
55 && ((in1->inheritable ? TRUE : FALSE)
56 == (in2->inheritable ? TRUE : FALSE))))
58 (*output)->start = MIN(in1->start, in2->start);
59 (*output)->end = MAX(in1->end, in2->end);
60 (*output)->inheritable =
61 (in1->inheritable || in2->inheritable) ? TRUE : FALSE;
62 return TRUE;
65 return FALSE;
68 /* pathname -> PATHNAME */
69 static svn_error_t *
70 parse_pathname(const char **input, const char *end,
71 svn_stringbuf_t **pathname, apr_pool_t *pool)
73 const char *curr = *input;
74 *pathname = svn_stringbuf_create("", pool);
76 while (curr < end && *curr != ':')
78 svn_stringbuf_appendbytes(*pathname, curr, 1);
79 curr++;
82 if ((*pathname)->len == 0)
83 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
84 _("No pathname preceeding ':'"));
85 *input = curr;
87 return SVN_NO_ERROR;
90 /* Helper for svn_rangelist_merge() and rangelist_intersect_or_remove().
92 If *LASTRANGE is not NULL it should point to the last element in REVLIST.
93 REVLIST must be sorted from lowest to highest revision and contain no
94 overlapping revision ranges. Any changes made to REVLIST will maintain
95 this guarantee.
97 If *LASTRANGE is NULL then push MRANGE to REVLIST.
99 If *LASTRANGE and MRANGE don't intersect then push MRANGE to REVLIST.
100 If they do intersect and have the same inheritability then combine the
101 ranges, updating *LASTRANGE to reflect the new combined range. If the
102 ranges intersect but differ in inheritability, then merge the ranges - see
103 the doc string for svn_mergeinfo_merge. This may result in a change to
104 *LASTRANGE's end field and the pushing of up to two new ranges on REVLIST.
106 e.g. *LASTRANGE: '4-10*' merged with MRANGE: '6'________
107 | | |
108 Update end field Push Account for trimmed
109 | | range from *LASTRANGE.
110 | | Push it last to
111 | | maintain sort order.
112 | | |
113 V V V
114 *LASTRANGE: '4-5*' MRANGE: '6' NEWRANGE: '6-10*'
116 Upon return, if any new ranges were pushed onto REVLIST, then set
117 *LASTRANGE to the last range pushed.
119 CONSIDER_INHERITANCE determines how to account for the inheritability of
120 MRANGE and *LASTRANGE when determining if they intersect. If
121 CONSIDER_INHERITANCE is TRUE, then only ranges with the same
122 inheritability can intersect and therefore be combined.
124 If DUP_MRANGE is TRUE then allocate a copy of MRANGE before pushing it
125 onto REVLIST.
127 static APR_INLINE void
128 combine_with_lastrange(svn_merge_range_t** lastrange,
129 svn_merge_range_t *mrange, svn_boolean_t dup_mrange,
130 apr_array_header_t *revlist,
131 svn_boolean_t consider_inheritance,
132 apr_pool_t *pool)
134 svn_merge_range_t *pushed_mrange_1 = NULL;
135 svn_merge_range_t *pushed_mrange_2 = NULL;
136 svn_boolean_t ranges_intersect = FALSE;
137 svn_boolean_t ranges_have_same_inheritance = FALSE;
139 if (*lastrange)
141 if ((*lastrange)->start <= mrange->end
142 && mrange->start <= (*lastrange)->end)
143 ranges_intersect = TRUE;
144 if ((*lastrange)->inheritable == mrange->inheritable)
145 ranges_have_same_inheritance = TRUE;
148 if (!(*lastrange)
149 || (!ranges_intersect || (!ranges_have_same_inheritance
150 && consider_inheritance)))
153 /* No *LASTRANGE
155 LASTRANGE and MRANGE don't intersect
157 LASTRANGE and MRANGE "intersect" but have different
158 inheritability and we are considering inheritance so
159 can't combined them...
161 ...In all these cases just push MRANGE onto *LASTRANGE. */
162 if (dup_mrange)
163 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
164 else
165 pushed_mrange_1 = mrange;
167 else /* MRANGE and *LASTRANGE intersect */
169 if (ranges_have_same_inheritance)
171 /* Intersecting ranges have the same inheritability
172 so just combine them. */
173 (*lastrange)->start = MIN((*lastrange)->start, mrange->start);
174 (*lastrange)->end = MAX((*lastrange)->end, mrange->end);
175 (*lastrange)->inheritable =
176 ((*lastrange)->inheritable || mrange->inheritable) ? TRUE : FALSE;
178 else /* Ranges intersect but have different
179 inheritability so merge the ranges. */
181 svn_revnum_t tmp_revnum;
183 /* Ranges have same starting revision. */
184 if ((*lastrange)->start == mrange->start)
186 if ((*lastrange)->end == mrange->end)
188 (*lastrange)->inheritable = TRUE;
190 else if ((*lastrange)->end > mrange->end)
192 if (!(*lastrange)->inheritable)
194 tmp_revnum = (*lastrange)->end;
195 (*lastrange)->end = mrange->end;
196 (*lastrange)->inheritable = TRUE;
197 if (dup_mrange)
198 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
199 else
200 pushed_mrange_1 = mrange;
201 pushed_mrange_1->start = pushed_mrange_1->start;
202 pushed_mrange_1->end = tmp_revnum;
203 *lastrange = pushed_mrange_1;
206 else /* (*lastrange)->end < mrange->end) */
208 if (mrange->inheritable)
210 (*lastrange)->inheritable = TRUE;
211 (*lastrange)->end = mrange->end;
213 else
215 if (dup_mrange)
216 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
217 else
218 pushed_mrange_1 = mrange;
219 pushed_mrange_1->start = (*lastrange)->end;
223 /* Ranges have same ending revision. (Same starting
224 and ending revisions already handled above.) */
225 else if ((*lastrange)->end == mrange->end)
227 if ((*lastrange)->start < mrange->start)
229 if (!(*lastrange)->inheritable)
231 (*lastrange)->end = mrange->start;
232 if (dup_mrange)
233 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
234 else
235 pushed_mrange_1 = mrange;
236 *lastrange = pushed_mrange_1;
239 else /* (*lastrange)->start > mrange->start */
241 (*lastrange)->start = mrange->start;
242 (*lastrange)->end = mrange->end;
243 (*lastrange)->inheritable = mrange->inheritable;
244 if (dup_mrange)
245 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
246 else
247 pushed_mrange_1 = mrange;
248 pushed_mrange_1->start = (*lastrange)->end;
249 pushed_mrange_1->inheritable = TRUE;
253 else /* Ranges have different starting and ending revisions. */
255 if ((*lastrange)->start < mrange->start)
257 /* If MRANGE is a proper subset of *LASTRANGE and
258 *LASTRANGE is inheritable there is nothing more
259 to do. */
260 if (!((*lastrange)->end > mrange->end
261 && (*lastrange)->inheritable))
263 tmp_revnum = (*lastrange)->end;
264 if (!(*lastrange)->inheritable)
265 (*lastrange)->end = mrange->start;
266 else
267 mrange->start = (*lastrange)->end;
268 if (dup_mrange)
269 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
270 else
271 pushed_mrange_1 = mrange;
273 if (tmp_revnum > mrange->end)
275 pushed_mrange_2 =
276 apr_palloc(pool, sizeof(*pushed_mrange_2));
277 pushed_mrange_2->start = mrange->end;
278 pushed_mrange_2->end = tmp_revnum;
279 pushed_mrange_2->inheritable =
280 (*lastrange)->inheritable;
282 mrange->inheritable = TRUE;
285 else /* ((*lastrange)->start > mrange->start) */
287 if ((*lastrange)->end < mrange->end)
289 pushed_mrange_2->start = (*lastrange)->end;
290 pushed_mrange_2->end = mrange->end;
291 pushed_mrange_2->inheritable = mrange->inheritable;
293 tmp_revnum = (*lastrange)->start;
294 (*lastrange)->start = mrange->start;
295 (*lastrange)->end = tmp_revnum;
296 (*lastrange)->inheritable = mrange->inheritable;
298 mrange->start = tmp_revnum;
299 mrange->end = pushed_mrange_2->start;
300 mrange->inheritable = TRUE;
302 else /* (*lastrange)->end > mrange->end */
304 pushed_mrange_2->start = mrange->end;
305 pushed_mrange_2->end = (*lastrange)->end;
306 pushed_mrange_2->inheritable =
307 (*lastrange)->inheritable;
309 tmp_revnum = (*lastrange)->start;
310 (*lastrange)->start = mrange->start;
311 (*lastrange)->end = tmp_revnum;
312 (*lastrange)->inheritable = mrange->inheritable;
314 mrange->start = tmp_revnum;
315 mrange->end = pushed_mrange_2->start;
316 mrange->inheritable = TRUE;
322 if (pushed_mrange_1)
324 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange_1;
325 *lastrange = pushed_mrange_1;
327 if (pushed_mrange_2)
329 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange_2;
330 *lastrange = pushed_mrange_2;
334 /* Convert a single svn_merge_range_t * back into an svn_stringbuf_t *. */
335 static svn_error_t *
336 range_to_stringbuf(svn_stringbuf_t **result, svn_merge_range_t *range,
337 apr_pool_t *pool)
339 if (range->start == range->end - 1)
340 *result = svn_stringbuf_createf(pool, "%ld%s", range->end,
341 range->inheritable
342 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
343 else
344 *result = svn_stringbuf_createf(pool, "%ld-%ld%s", range->start + 1,
345 range->end, range->inheritable
346 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
347 return SVN_NO_ERROR;
350 /* Helper for svn_mergeinfo_parse() via parse_revlist().
352 Similar to combine_with_lastrange() but enforces the some of the
353 restrictions noted in svn_mergeinfo_parse() on otherwise grammatically
354 correct rangelists, specifically the prohibitions on:
356 1) Overlapping revision ranges
358 2) Unordered revision ranges
360 Returns an SVN_ERR_MERGEINFO_PARSE_ERROR error if any of these rules
361 are violated. The restriction on revision ranges with a start revision
362 greater than or equal to its end revision is handled in parse_revlist().
364 Unlike combine_with_lastrange() this function *always* considers
365 inheritance, so only adjacent revision ranges with the same
366 inheritability are ever combined. */
367 static svn_error_t *
368 combine_with_adjacent_lastrange(svn_merge_range_t **lastrange,
369 svn_merge_range_t *mrange,
370 svn_boolean_t dup_mrange,
371 apr_array_header_t *revlist,
372 apr_pool_t *pool)
374 svn_merge_range_t *pushed_mrange = mrange;
376 if (*lastrange)
378 svn_stringbuf_t *r1, *r2;
380 if ((*lastrange)->start <= mrange->end
381 && mrange->start <= (*lastrange)->end)
383 /* The ranges intersect. */
384 SVN_ERR(range_to_stringbuf(&r1, *lastrange, pool));
385 SVN_ERR(range_to_stringbuf(&r2, mrange, pool));
387 /* svn_mergeinfo_parse promises to combine adjacent
388 ranges, but not overlapping ranges. */
389 if (mrange->start < (*lastrange)->end)
391 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
392 _("Parsing of overlapping revision "
393 "ranges '%s' and '%s' is not "
394 "supported"), r1->data, r2->data);
396 else if ((*lastrange)->inheritable == mrange->inheritable)
398 /* Combine adjacent ranges with the same inheritability. */
399 (*lastrange)->end = mrange->end;
400 return SVN_NO_ERROR;
403 else if ((*lastrange)->start > mrange->start)
405 SVN_ERR(range_to_stringbuf(&r1, *lastrange, pool));
406 SVN_ERR(range_to_stringbuf(&r2, mrange, pool));
407 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
408 _("Unable to parse unordered revision "
409 "ranges '%s' and '%s'"),
410 r1->data, r2->data);
414 if (dup_mrange)
415 pushed_mrange = svn_merge_range_dup(mrange, pool);
416 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange;
417 *lastrange = pushed_mrange;
418 return SVN_NO_ERROR;
421 /* Helper for svn_mergeinfo_parse()
423 revisionlist -> (revisionelement)(COMMA revisionelement)*
424 revisionrange -> REVISION "-" REVISION("*")
425 revisionelement -> revisionrange | REVISION("*")
427 PATHNAME is the path this revisionlist is mapped to. It is
428 used only for producing a more descriptive error message.
430 static svn_error_t *
431 parse_revlist(const char **input, const char *end,
432 apr_array_header_t *revlist, const char *pathname,
433 apr_pool_t *pool)
435 const char *curr = *input;
436 svn_merge_range_t *lastrange = NULL;
438 /* Eat any leading horizontal white-space before the rangelist. */
439 while (curr < end && *curr != '\n' && isspace(*curr))
440 curr++;
442 if (*curr == '\n' || curr == end)
444 /* Empty range list. */
445 *input = curr;
446 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
447 _("Mergeinfo for '%s' maps to an "
448 "empty revision range"), pathname);
451 while (curr < end && *curr != '\n')
453 /* Parse individual revisions or revision ranges. */
454 svn_merge_range_t *mrange = apr_pcalloc(pool, sizeof(*mrange));
455 svn_revnum_t firstrev;
457 SVN_ERR(svn_revnum_parse(&firstrev, curr, &curr));
458 if (*curr != '-' && *curr != '\n' && *curr != ',' && *curr != '*'
459 && curr != end)
460 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
461 _("Invalid character '%c' found in revision "
462 "list"), *curr);
463 mrange->start = firstrev - 1;
464 mrange->end = firstrev;
465 mrange->inheritable = TRUE;
467 if (*curr == '-')
469 svn_revnum_t secondrev;
471 curr++;
472 SVN_ERR(svn_revnum_parse(&secondrev, curr, &curr));
473 if (firstrev > secondrev)
474 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
475 _("Unable to parse reversed revision "
476 "range '%ld-%ld'"),
477 firstrev, secondrev);
478 else if (firstrev == secondrev)
479 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
480 _("Unable to parse revision range "
481 "'%ld-%ld' with same start and end "
482 "revisions"), firstrev, secondrev);
483 mrange->end = secondrev;
486 if (*curr == '\n' || curr == end)
488 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange, FALSE,
489 revlist, pool));
490 *input = curr;
491 return SVN_NO_ERROR;
493 else if (*curr == ',')
495 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange, FALSE,
496 revlist, pool));
497 curr++;
499 else if (*curr == '*')
501 mrange->inheritable = FALSE;
502 curr++;
503 if (*curr == ',' || *curr == '\n' || curr == end)
505 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange,
506 FALSE, revlist, pool));
507 if (*curr == ',')
509 curr++;
511 else
513 *input = curr;
514 return SVN_NO_ERROR;
517 else
519 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
520 _("Invalid character '%c' found in "
521 "range list"), *curr);
524 else
526 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
527 _("Invalid character '%c' found in "
528 "range list"), *curr);
532 if (*curr != '\n')
533 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
534 _("Range list parsing ended before hitting "
535 "newline"));
536 *input = curr;
537 return SVN_NO_ERROR;
540 /* revisionline -> PATHNAME COLON revisionlist */
541 static svn_error_t *
542 parse_revision_line(const char **input, const char *end, apr_hash_t *hash,
543 apr_pool_t *pool)
545 svn_stringbuf_t *pathname;
546 apr_array_header_t *revlist = apr_array_make(pool, 1,
547 sizeof(svn_merge_range_t *));
549 SVN_ERR(parse_pathname(input, end, &pathname, pool));
551 if (*(*input) != ':')
552 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
553 _("Pathname not terminated by ':'"));
555 *input = *input + 1;
557 SVN_ERR(parse_revlist(input, end, revlist, pathname->data, pool));
559 if (*input != end && *(*input) != '\n')
560 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
561 _("Could not find end of line in range list line "
562 "in '%s'"), *input);
564 if (*input != end)
565 *input = *input + 1;
567 qsort(revlist->elts, revlist->nelts, revlist->elt_size,
568 svn_sort_compare_ranges);
569 apr_hash_set(hash, pathname->data, APR_HASH_KEY_STRING, revlist);
571 return SVN_NO_ERROR;
574 /* top -> revisionline (NEWLINE revisionline)* */
575 static svn_error_t *
576 parse_top(const char **input, const char *end, apr_hash_t *hash,
577 apr_pool_t *pool)
579 while (*input < end)
580 SVN_ERR(parse_revision_line(input, end, hash, pool));
582 return SVN_NO_ERROR;
585 /* Parse mergeinfo. */
586 svn_error_t *
587 svn_mergeinfo_parse(apr_hash_t **mergeinfo,
588 const char *input,
589 apr_pool_t *pool)
591 *mergeinfo = apr_hash_make(pool);
592 return parse_top(&input, input + strlen(input), *mergeinfo, pool);
596 /* Merge revision list RANGELIST into *MERGEINFO, doing some trivial
597 attempts to combine ranges as we go. */
598 svn_error_t *
599 svn_rangelist_merge(apr_array_header_t **rangelist,
600 apr_array_header_t *changes,
601 apr_pool_t *pool)
603 int i, j;
604 svn_merge_range_t *lastrange = NULL;
605 apr_array_header_t *output = apr_array_make(pool, 1,
606 sizeof(svn_merge_range_t *));
607 i = 0;
608 j = 0;
609 while (i < (*rangelist)->nelts && j < changes->nelts)
611 svn_merge_range_t *elt1, *elt2;
612 int res;
614 elt1 = APR_ARRAY_IDX(*rangelist, i, svn_merge_range_t *);
615 elt2 = APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
617 res = svn_sort_compare_ranges(&elt1, &elt2);
618 if (res == 0)
620 /* Only when merging two non-inheritable ranges is the result also
621 non-inheritable. In all other cases ensure an inheritiable
622 result. */
623 if (elt1->inheritable || elt2->inheritable)
624 elt1->inheritable = TRUE;
625 combine_with_lastrange(&lastrange, elt1, TRUE, output,
626 FALSE, pool);
627 i++;
628 j++;
630 else if (res < 0)
632 combine_with_lastrange(&lastrange, elt1, TRUE, output,
633 FALSE, pool);
634 i++;
636 else
638 combine_with_lastrange(&lastrange, elt2, TRUE, output,
639 FALSE, pool);
640 j++;
643 /* Copy back any remaining elements.
644 Only one of these loops should end up running, if anything. */
646 assert (!(i < (*rangelist)->nelts && j < changes->nelts));
648 for (; i < (*rangelist)->nelts; i++)
650 svn_merge_range_t *elt = APR_ARRAY_IDX(*rangelist, i,
651 svn_merge_range_t *);
652 combine_with_lastrange(&lastrange, elt, TRUE, output,
653 FALSE, pool);
657 for (; j < changes->nelts; j++)
659 svn_merge_range_t *elt = APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
660 combine_with_lastrange(&lastrange, elt, TRUE, output,
661 FALSE, pool);
664 *rangelist = output;
665 return SVN_NO_ERROR;
668 static svn_boolean_t
669 range_intersect(svn_merge_range_t *first, svn_merge_range_t *second,
670 svn_boolean_t consider_inheritance)
672 return (first->start + 1 <= second->end)
673 && (second->start + 1 <= first->end)
674 && (!consider_inheritance
675 || (!(first->inheritable) == !(second->inheritable)));
678 static svn_boolean_t
679 range_contains(svn_merge_range_t *first, svn_merge_range_t *second,
680 svn_boolean_t consider_inheritance)
682 return (first->start <= second->start) && (second->end <= first->end)
683 && (!consider_inheritance
684 || (!(first->inheritable) == !(second->inheritable)));
687 /* Swap start and end fields of RANGE. */
688 static void
689 range_swap_endpoints(svn_merge_range_t *range)
691 svn_revnum_t swap = range->start;
692 range->start = range->end;
693 range->end = swap;
696 svn_error_t *
697 svn_rangelist_reverse(apr_array_header_t *rangelist, apr_pool_t *pool)
699 int i, swap_index;
700 svn_merge_range_t range;
701 for (i = 0; i < rangelist->nelts / 2; i++)
703 swap_index = rangelist->nelts - i - 1;
704 range = *APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
705 *APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *) =
706 *APR_ARRAY_IDX(rangelist, swap_index, svn_merge_range_t *);
707 *APR_ARRAY_IDX(rangelist, swap_index, svn_merge_range_t *) = range;
708 range_swap_endpoints(APR_ARRAY_IDX(rangelist, swap_index,
709 svn_merge_range_t *));
710 range_swap_endpoints(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *));
713 /* If there's an odd number of elements, we still need to swap the
714 end points of the remaining range. */
715 if (rangelist->nelts % 2 == 1)
716 range_swap_endpoints(APR_ARRAY_IDX(rangelist, rangelist->nelts / 2,
717 svn_merge_range_t *));
719 return SVN_NO_ERROR;
722 /* Either remove any overlapping ranges described by ERASER from
723 WHITEBOARD (when DO_REMOVE is TRUE), or capture the overlap, and
724 place the remaining or overlapping ranges in OUTPUT. */
725 /* ### FIXME: Some variables names and inline comments for this method
726 ### are legacy from when it was solely the remove() impl. */
727 static svn_error_t *
728 rangelist_intersect_or_remove(apr_array_header_t **output,
729 apr_array_header_t *eraser,
730 apr_array_header_t *whiteboard,
731 svn_boolean_t do_remove,
732 svn_boolean_t consider_inheritance,
733 apr_pool_t *pool)
735 int i, j, lasti;
736 svn_merge_range_t *lastrange = NULL;
737 svn_merge_range_t wboardelt;
739 *output = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
741 i = 0;
742 j = 0;
743 lasti = -1; /* Initialized to a value that "i" will never be. */
745 while (i < whiteboard->nelts && j < eraser->nelts)
747 svn_merge_range_t *elt1, *elt2;
749 elt2 = APR_ARRAY_IDX(eraser, j, svn_merge_range_t *);
751 /* Instead of making a copy of the entire array of whiteboard
752 elements, we just keep a copy of the current whiteboard element
753 that needs to be used, and modify our copy if necessary. */
754 if (i != lasti)
756 wboardelt = *(APR_ARRAY_IDX(whiteboard, i, svn_merge_range_t *));
757 elt1 = &wboardelt;
758 lasti = i;
761 /* If the whiteboard range is contained completely in the
762 eraser, we increment the whiteboard.
763 If the ranges intersect, and match exactly, we increment both
764 eraser and whiteboard.
765 Otherwise, we have to generate a range for the left part of
766 the removal of eraser from whiteboard, and possibly change
767 the whiteboard to the remaining portion of the right part of
768 the removal, to test against. */
769 if (range_contains(elt2, elt1, consider_inheritance))
771 if (!do_remove)
772 combine_with_lastrange(&lastrange, elt1, TRUE, *output,
773 consider_inheritance, pool);
775 i++;
777 if (elt1->start == elt2->start && elt1->end == elt2->end)
778 j++;
780 else if (range_intersect(elt2, elt1, consider_inheritance))
782 if (elt1->start < elt2->start)
784 /* The whiteboard range starts before the eraser range. */
785 svn_merge_range_t tmp_range;
786 tmp_range.inheritable = elt1->inheritable;
787 if (do_remove)
789 /* Retain the range that falls before the eraser start. */
790 tmp_range.start = elt1->start;
791 tmp_range.end = elt2->start;
793 else
795 /* Retain the range that falls between the eraser
796 start and whiteboard end. */
797 tmp_range.start = elt2->start;
798 tmp_range.end = elt1->end;
801 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
802 *output, consider_inheritance, pool);
805 /* Set up the rest of the whiteboard range for further
806 processing. */
807 if (elt1->end > elt2->end)
809 /* The whiteboard range ends after the eraser range. */
810 if (!do_remove)
812 /* Partial overlap. */
813 svn_merge_range_t tmp_range;
814 tmp_range.start = elt1->start;
815 tmp_range.end = elt2->end;
816 tmp_range.inheritable = elt1->inheritable;
817 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
818 *output, consider_inheritance, pool);
821 wboardelt.start = elt2->end;
822 wboardelt.end = elt1->end;
824 else
825 i++;
827 else /* ranges don't intersect */
829 /* See which side of the whiteboard the eraser is on. If it
830 is on the left side, we need to move the eraser.
832 If it is on past the whiteboard on the right side, we
833 need to output the whiteboard and increment the
834 whiteboard. */
835 if (svn_sort_compare_ranges(&elt2, &elt1) < 0)
836 j++;
837 else
839 if (!lastrange || !combine_ranges(&lastrange, lastrange, elt1,
840 consider_inheritance))
842 if (do_remove)
844 lastrange = svn_merge_range_dup(elt1, pool);
845 APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange;
848 i++;
853 if (do_remove)
855 /* Copy the current whiteboard element if we didn't hit the end
856 of the whiteboard, and we still had it around. This element
857 may have been touched, so we can't just walk the whiteboard
858 array, we have to use our copy. This case only happens when
859 we ran out of eraser before whiteboard, *and* we had changed
860 the whiteboard element. */
861 if (i == lasti && i < whiteboard->nelts)
863 combine_with_lastrange(&lastrange, &wboardelt, TRUE, *output,
864 consider_inheritance, pool);
865 i++;
868 /* Copy any other remaining untouched whiteboard elements. */
869 for (; i < whiteboard->nelts; i++)
871 svn_merge_range_t *elt = APR_ARRAY_IDX(whiteboard, i,
872 svn_merge_range_t *);
874 combine_with_lastrange(&lastrange, elt, TRUE, *output,
875 consider_inheritance, pool);
879 return SVN_NO_ERROR;
883 /* Expected to handle all the range overlap cases: non, partial, full */
884 svn_error_t *
885 svn_rangelist_intersect(apr_array_header_t **output,
886 apr_array_header_t *rangelist1,
887 apr_array_header_t *rangelist2,
888 apr_pool_t *pool)
890 return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE,
891 TRUE, pool);
894 svn_error_t *
895 svn_rangelist_remove(apr_array_header_t **output,
896 apr_array_header_t *eraser,
897 apr_array_header_t *whiteboard,
898 svn_boolean_t consider_inheritance,
899 apr_pool_t *pool)
901 return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE,
902 consider_inheritance, pool);
905 /* Output deltas via *DELETED and *ADDED, which will never be @c NULL.
907 The following diagrams illustrate some common range delta scenarios:
909 (from) deleted
910 r0 <===========(=========)============[=========]===========> rHEAD
911 [to] added
913 (from) deleted deleted
914 r0 <===========(=========[============]=========)===========> rHEAD
915 [to]
917 (from) deleted
918 r0 <===========(=========[============)=========]===========> rHEAD
919 [to] added
921 (from) deleted
922 r0 <===========[=========(============]=========)===========> rHEAD
923 [to] added
925 (from)
926 r0 <===========[=========(============)=========]===========> rHEAD
927 [to] added added
929 (from) d d d
930 r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
931 [to] a a a a a a a
933 svn_error_t *
934 svn_rangelist_diff(apr_array_header_t **deleted, apr_array_header_t **added,
935 apr_array_header_t *from, apr_array_header_t *to,
936 svn_boolean_t consider_inheritance,
937 apr_pool_t *pool)
939 /* The items that are present in from, but not in to, must have been
940 deleted. */
941 SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance,
942 pool));
943 /* The items that are present in to, but not in from, must have been
944 added. */
945 SVN_ERR(svn_rangelist_remove(added, from, to, consider_inheritance, pool));
946 return SVN_NO_ERROR;
949 apr_uint64_t
950 svn_rangelist_count_revs(apr_array_header_t *rangelist)
952 apr_uint64_t nbr_revs = 0;
953 int i;
955 for (i = 0; i < rangelist->nelts; i++)
957 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
958 svn_merge_range_t *);
959 nbr_revs += range->end - range->start;
962 return nbr_revs;
965 svn_error_t *
966 svn_rangelist_to_revs(apr_array_header_t **revs,
967 const apr_array_header_t *rangelist,
968 apr_pool_t *pool)
970 int i;
972 *revs = apr_array_make(pool, rangelist->nelts, sizeof(svn_revnum_t));
974 for (i = 0; i < rangelist->nelts; i++)
976 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
977 svn_merge_range_t *);
978 svn_revnum_t rev = range->start + 1;
980 while (rev <= range->end)
982 APR_ARRAY_PUSH(*revs, svn_revnum_t) = rev;
983 rev += 1;
987 return SVN_NO_ERROR;
990 /* Record deletions and additions of entire range lists (by path
991 presence), and delegate to svn_rangelist_diff() for delta
992 calculations on a specific path. */
993 /* ### TODO: Merge implementation with
994 ### libsvn_subr/sorts.c:svn_prop_diffs(). Factor out a generic
995 ### hash diffing function for addition to APR's apr_hash.h API. */
996 static svn_error_t *
997 walk_mergeinfo_hash_for_diff(apr_hash_t *from, apr_hash_t *to,
998 apr_hash_t *deleted, apr_hash_t *added,
999 svn_boolean_t consider_inheritance,
1000 apr_pool_t *pool)
1002 apr_hash_index_t *hi;
1003 const void *key;
1004 void *val;
1005 const char *path;
1006 apr_array_header_t *from_rangelist, *to_rangelist;
1008 /* Handle path deletions and differences. */
1009 for (hi = apr_hash_first(pool, from); hi; hi = apr_hash_next(hi))
1011 apr_hash_this(hi, &key, NULL, &val);
1012 path = key;
1013 from_rangelist = val;
1015 /* If the path is not present at all in the "to" hash, the
1016 entire "from" rangelist is a deletion. Paths which are
1017 present in the "to" hash require closer scrutiny. */
1018 to_rangelist = apr_hash_get(to, path, APR_HASH_KEY_STRING);
1019 if (to_rangelist)
1021 /* Record any deltas (additions or deletions). */
1022 apr_array_header_t *deleted_rangelist, *added_rangelist;
1023 svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
1024 from_rangelist, to_rangelist,
1025 consider_inheritance, pool);
1026 if (deleted && deleted_rangelist->nelts > 0)
1027 apr_hash_set(deleted, apr_pstrdup(pool, path),
1028 APR_HASH_KEY_STRING, deleted_rangelist);
1029 if (added && added_rangelist->nelts > 0)
1030 apr_hash_set(added, apr_pstrdup(pool, path),
1031 APR_HASH_KEY_STRING, added_rangelist);
1033 else if (deleted)
1034 apr_hash_set(deleted, apr_pstrdup(pool, path), APR_HASH_KEY_STRING,
1035 svn_rangelist_dup(from_rangelist, pool));
1038 /* Handle path additions. */
1039 if (!added)
1040 return SVN_NO_ERROR;
1042 for (hi = apr_hash_first(pool, to); hi; hi = apr_hash_next(hi))
1044 apr_hash_this(hi, &key, NULL, &val);
1045 path = key;
1046 to_rangelist = val;
1048 /* If the path is not present in the "from" hash, the entire
1049 "to" rangelist is an addition. */
1050 if (apr_hash_get(from, path, APR_HASH_KEY_STRING) == NULL)
1051 apr_hash_set(added, apr_pstrdup(pool, path), APR_HASH_KEY_STRING,
1052 svn_rangelist_dup(to_rangelist, pool));
1055 return SVN_NO_ERROR;
1058 svn_error_t *
1059 svn_mergeinfo_diff(apr_hash_t **deleted, apr_hash_t **added,
1060 apr_hash_t *from, apr_hash_t *to,
1061 svn_boolean_t consider_inheritance,
1062 apr_pool_t *pool)
1064 if (from && to == NULL)
1066 *deleted = svn_mergeinfo_dup(from, pool);
1067 *added = apr_hash_make(pool);
1069 else if (from == NULL && to)
1071 *deleted = apr_hash_make(pool);
1072 *added = svn_mergeinfo_dup(to, pool);
1074 else
1076 *deleted = apr_hash_make(pool);
1077 *added = apr_hash_make(pool);
1079 if (from && to)
1081 SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added,
1082 consider_inheritance, pool));
1086 return SVN_NO_ERROR;
1089 svn_error_t *
1090 svn_mergeinfo__equals(svn_boolean_t *is_equal,
1091 apr_hash_t *info1,
1092 apr_hash_t *info2,
1093 svn_boolean_t consider_inheritance,
1094 apr_pool_t *pool)
1096 if (apr_hash_count(info1) == apr_hash_count(info2))
1098 apr_hash_t *deleted, *added;
1099 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, info1, info2,
1100 consider_inheritance, pool));
1101 *is_equal = apr_hash_count(deleted) == 0 && apr_hash_count(added) == 0;
1103 else
1105 *is_equal = FALSE;
1107 return SVN_NO_ERROR;
1110 svn_error_t *
1111 svn_mergeinfo_merge(apr_hash_t *mergeinfo, apr_hash_t *changes,
1112 apr_pool_t *pool)
1114 apr_array_header_t *sorted1, *sorted2;
1115 int i, j;
1117 sorted1 = svn_sort__hash(mergeinfo, svn_sort_compare_items_as_paths, pool);
1118 sorted2 = svn_sort__hash(changes, svn_sort_compare_items_as_paths, pool);
1120 i = 0;
1121 j = 0;
1122 while (i < sorted1->nelts && j < sorted2->nelts)
1124 svn_sort__item_t elt1, elt2;
1125 int res;
1127 elt1 = APR_ARRAY_IDX(sorted1, i, svn_sort__item_t);
1128 elt2 = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1129 res = svn_sort_compare_items_as_paths(&elt1, &elt2);
1131 if (res == 0)
1133 apr_array_header_t *rl1, *rl2;
1135 rl1 = elt1.value;
1136 rl2 = elt2.value;
1138 SVN_ERR(svn_rangelist_merge(&rl1, rl2,
1139 pool));
1140 apr_hash_set(mergeinfo, elt1.key, elt1.klen, rl1);
1141 i++;
1142 j++;
1144 else if (res < 0)
1146 i++;
1148 else
1150 apr_hash_set(mergeinfo, elt2.key, elt2.klen, elt2.value);
1151 j++;
1155 /* Copy back any remaining elements from the second hash. */
1156 for (; j < sorted2->nelts; j++)
1158 svn_sort__item_t elt = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1159 apr_hash_set(mergeinfo, elt.key, elt.klen, elt.value);
1162 return SVN_NO_ERROR;
1165 svn_error_t *
1166 svn_mergeinfo_intersect(apr_hash_t **mergeinfo,
1167 apr_hash_t *mergeinfo1,
1168 apr_hash_t *mergeinfo2,
1169 apr_pool_t *pool)
1171 apr_hash_index_t *hi;
1173 *mergeinfo = apr_hash_make(pool);
1175 /* ### TODO(reint): Do we care about the case when a path in one
1176 ### mergeinfo hash has inheritable mergeinfo, and in the other
1177 ### has non-inhertiable mergeinfo? It seems like that path
1178 ### itself should really be an intersection, while child paths
1179 ### should not be... */
1180 for (hi = apr_hash_first(apr_hash_pool_get(mergeinfo1), mergeinfo1);
1181 hi; hi = apr_hash_next(hi))
1183 apr_array_header_t *rangelist;
1184 const void *path;
1185 void *val;
1186 apr_hash_this(hi, &path, NULL, &val);
1188 rangelist = apr_hash_get(mergeinfo2, path, APR_HASH_KEY_STRING);
1189 if (rangelist)
1191 SVN_ERR(svn_rangelist_intersect(&rangelist,
1192 (apr_array_header_t *) val,
1193 rangelist, pool));
1194 if (rangelist->nelts > 0)
1195 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
1198 return SVN_NO_ERROR;
1201 svn_error_t *
1202 svn_mergeinfo_remove(apr_hash_t **mergeinfo, apr_hash_t *eraser,
1203 apr_hash_t *whiteboard, apr_pool_t *pool)
1205 *mergeinfo = apr_hash_make(pool);
1206 SVN_ERR(walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL,
1207 TRUE, pool));
1208 return SVN_NO_ERROR;
1211 svn_error_t *
1212 svn_rangelist_to_stringbuf(svn_stringbuf_t **output,
1213 const apr_array_header_t *rangelist,
1214 apr_pool_t *pool)
1216 *output = svn_stringbuf_create("", pool);
1218 if (rangelist->nelts > 0)
1220 int i;
1221 svn_merge_range_t *range;
1222 svn_stringbuf_t *toappend;
1224 /* Handle the elements that need commas at the end. */
1225 for (i = 0; i < rangelist->nelts - 1; i++)
1227 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1228 SVN_ERR(range_to_stringbuf(&toappend, range, pool));
1229 svn_stringbuf_appendstr(*output, toappend);
1230 svn_stringbuf_appendcstr(*output, ",");
1233 /* Now handle the last element, which needs no comma. */
1234 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1235 SVN_ERR(range_to_stringbuf(&toappend, range, pool));
1236 svn_stringbuf_appendstr(*output, toappend);
1239 return SVN_NO_ERROR;
1242 svn_error_t *
1243 svn_mergeinfo_to_stringbuf(svn_stringbuf_t **output, apr_hash_t *input,
1244 apr_pool_t *pool)
1246 *output = svn_stringbuf_create("", pool);
1248 if (apr_hash_count(input) > 0)
1250 apr_array_header_t *sorted =
1251 svn_sort__hash(input, svn_sort_compare_items_as_paths, pool);
1252 svn_sort__item_t elt;
1253 svn_stringbuf_t *revlist, *combined;
1254 int i;
1256 /* Handle the elements that need newlines at the end. */
1257 for (i = 0; i < sorted->nelts - 1; i++)
1259 elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
1261 SVN_ERR(svn_rangelist_to_stringbuf(&revlist, elt.value, pool));
1262 combined = svn_stringbuf_createf(pool, "%s:%s\n", (char *) elt.key,
1263 revlist->data);
1264 svn_stringbuf_appendstr(*output, combined);
1267 /* Now handle the last element, which is not newline terminated. */
1268 elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
1270 SVN_ERR(svn_rangelist_to_stringbuf(&revlist, elt.value, pool));
1271 combined = svn_stringbuf_createf(pool, "%s:%s", (char *) elt.key,
1272 revlist->data);
1273 svn_stringbuf_appendstr(*output, combined);
1276 return SVN_NO_ERROR;
1279 svn_error_t *
1280 svn_mergeinfo__to_string(svn_string_t **output, apr_hash_t *input,
1281 apr_pool_t *pool)
1283 if (apr_hash_count(input) > 0)
1285 svn_stringbuf_t *mergeinfo_buf;
1286 SVN_ERR(svn_mergeinfo_to_stringbuf(&mergeinfo_buf, input, pool));
1287 *output = svn_string_create_from_buf(mergeinfo_buf, pool);
1289 else
1291 *output = svn_string_create("", pool);
1293 return SVN_NO_ERROR;
1296 /* Perform an in-place sort of the rangelists in a mergeinfo hash. */
1297 svn_error_t*
1298 svn_mergeinfo_sort(apr_hash_t *input, apr_pool_t *pool)
1300 apr_hash_index_t *hi;
1301 void *val;
1303 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
1305 apr_array_header_t *rl;
1306 apr_hash_this(hi, NULL, NULL, &val);
1308 rl = val;
1309 qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges);
1311 return SVN_NO_ERROR;
1314 apr_hash_t *
1315 svn_mergeinfo_dup(apr_hash_t *mergeinfo, apr_pool_t *pool)
1317 apr_hash_t *new_mergeinfo = apr_hash_make(pool);
1318 apr_hash_index_t *hi;
1319 const void *path;
1320 apr_ssize_t pathlen;
1321 void *rangelist;
1323 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1325 apr_hash_this(hi, &path, &pathlen, &rangelist);
1326 apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen,
1327 svn_rangelist_dup((apr_array_header_t *) rangelist, pool));
1330 return new_mergeinfo;
1333 svn_error_t *
1334 svn_mergeinfo_inheritable(apr_hash_t **output,
1335 apr_hash_t *mergeinfo,
1336 const char *path,
1337 svn_revnum_t start,
1338 svn_revnum_t end,
1339 apr_pool_t *pool)
1341 apr_hash_index_t *hi;
1342 const void *key;
1343 apr_ssize_t keylen;
1344 void *rangelist;
1346 apr_hash_t *inheritable_mergeinfo = apr_hash_make(pool);
1347 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1349 apr_array_header_t *inheritable_rangelist;
1350 apr_hash_this(hi, &key, &keylen, &rangelist);
1351 if (!path || svn_path_compare_paths(path, (const char *)key) == 0)
1352 SVN_ERR(svn_rangelist_inheritable(&inheritable_rangelist,
1353 (apr_array_header_t *) rangelist,
1354 start, end, pool));
1355 else
1356 inheritable_rangelist =
1357 svn_rangelist_dup((apr_array_header_t *)rangelist, pool);
1358 apr_hash_set(inheritable_mergeinfo,
1359 apr_pstrmemdup(pool, key, keylen), keylen,
1360 inheritable_rangelist);
1362 *output = inheritable_mergeinfo;
1363 return SVN_NO_ERROR;
1366 svn_error_t *
1367 svn_rangelist_inheritable(apr_array_header_t **inheritable_rangelist,
1368 apr_array_header_t *rangelist,
1369 svn_revnum_t start,
1370 svn_revnum_t end,
1371 apr_pool_t *pool)
1373 *inheritable_rangelist = apr_array_make(pool, 1,
1374 sizeof(svn_merge_range_t *));
1375 if (rangelist->nelts)
1377 if (!SVN_IS_VALID_REVNUM(start)
1378 || !SVN_IS_VALID_REVNUM(end)
1379 || end < start)
1381 int i;
1382 /* We want all non-inheritable ranges removed. */
1383 for (i = 0; i < rangelist->nelts; i++)
1385 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
1386 svn_merge_range_t *);
1387 if (range->inheritable)
1389 svn_merge_range_t *inheritable_range =
1390 apr_palloc(pool, sizeof(*inheritable_range));
1391 inheritable_range->start = range->start;
1392 inheritable_range->end = range->end;
1393 inheritable_range->inheritable = TRUE;
1394 APR_ARRAY_PUSH(*inheritable_rangelist,
1395 svn_merge_range_t *) = range;
1399 else
1401 /* We want only the non-inheritable ranges bound by START
1402 and END removed. */
1403 apr_array_header_t *ranges_inheritable =
1404 apr_array_make(pool, 0, sizeof(svn_merge_range_t *));
1405 svn_merge_range_t *range = apr_palloc(pool, sizeof(*range));
1407 range->start = start;
1408 range->end = end;
1409 range->inheritable = FALSE;
1410 APR_ARRAY_PUSH(ranges_inheritable, svn_merge_range_t *) = range;
1412 if (rangelist->nelts)
1413 SVN_ERR(svn_rangelist_remove(inheritable_rangelist,
1414 ranges_inheritable,
1415 rangelist,
1416 TRUE,
1417 pool));
1420 return SVN_NO_ERROR;
1423 svn_boolean_t
1424 svn_mergeinfo__remove_empty_rangelists(apr_hash_t *mergeinfo,
1425 apr_pool_t *pool)
1427 apr_hash_index_t *hi;
1428 svn_boolean_t removed_some_ranges = FALSE;
1430 if (mergeinfo)
1432 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1434 const void *key;
1435 void *value;
1436 const char *path;
1437 apr_array_header_t *rangelist;
1439 apr_hash_this(hi, &key, NULL, &value);
1440 path = key;
1441 rangelist = value;
1443 if (rangelist->nelts == 0)
1445 apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, NULL);
1446 removed_some_ranges = TRUE;
1450 return removed_some_ranges;
1453 apr_array_header_t *
1454 svn_rangelist_dup(apr_array_header_t *rangelist, apr_pool_t *pool)
1456 apr_array_header_t *new_rl = apr_array_make(pool, rangelist->nelts,
1457 sizeof(svn_merge_range_t *));
1458 int i;
1460 for (i = 0; i < rangelist->nelts; i++)
1462 APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) =
1463 svn_merge_range_dup(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
1464 pool);
1467 return new_rl;
1470 svn_boolean_t
1471 svn_range_compact(svn_merge_range_t **range_1,
1472 svn_merge_range_t **range_2)
1474 svn_boolean_t is_compacted = FALSE;
1475 svn_boolean_t range_1_is_reversed, range_2_is_reversed;
1477 /* Wave the white flag if out preconditions are not met. */
1478 if (!*range_1
1479 || !*range_2
1480 || !SVN_IS_VALID_REVNUM(*range_1)
1481 || !SVN_IS_VALID_REVNUM(*range_2))
1482 return FALSE;
1484 /* "Normalize" the ranges so start <= end. */
1485 range_1_is_reversed = (*range_1)->start > (*range_1)->end ? TRUE : FALSE;
1486 range_2_is_reversed = (*range_2)->start > (*range_2)->end ? TRUE : FALSE;
1487 if (range_1_is_reversed)
1488 range_swap_endpoints(*range_1);
1489 if (range_2_is_reversed)
1490 range_swap_endpoints(*range_2);
1492 /* Do the ranges overlap?
1493 Range overlapping detection algorithm from
1494 http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap */
1495 if ((*range_1)->start <= (*range_2)->end
1496 && (*range_2)->start <= (*range_1)->end)
1498 /* If the ranges are both in the same direction simply combine them. */
1499 if (range_1_is_reversed == range_2_is_reversed)
1501 (*range_1)->start = MIN((*range_1)->start, (*range_2)->start);
1502 (*range_1)->end = MAX((*range_1)->end, (*range_2)->end);
1503 *range_2 = NULL;
1505 else /* *Exactly* one of the ranges is a reverse range. */
1507 svn_revnum_t range_tmp;
1509 if ((*range_1)->start == (*range_2)->start)
1511 if ((*range_1)->end == (*range_2)->end)
1513 /* A range and its reverse just cancel each other out. */
1514 *range_1 = *range_2 = NULL;
1516 else
1518 (*range_1)->start = (*range_1)->end;
1519 (*range_1)->end = (*range_2)->end;
1520 *range_2 = NULL;
1521 /* RANGE_2 is a superset of RANGE_1, the intersecting
1522 portions of each range cancel each other out.
1523 The resulting compacted range is stored in RANGE_1 so
1524 it takes on the reversed property of RANGE_2. */
1525 range_1_is_reversed = range_2_is_reversed;
1528 else /* (*range_1)->start != (*range_2)->start) */
1530 if ((*range_1)->end > (*range_2)->end)
1532 /* RANGE_1 is a superset of RANGE_2 */
1533 if ((*range_1)->start < (*range_2)->start)
1535 range_tmp = (*range_1)->end;
1536 (*range_1)->end = (*range_2)->start;
1537 (*range_2)->start = (*range_2)->end;
1538 (*range_2)->end = range_tmp;
1539 range_2_is_reversed = range_1_is_reversed;
1541 else
1543 range_tmp = (*range_1)->start;
1544 (*range_1)->start = (*range_2)->end;
1545 (*range_2)->end = range_tmp;
1548 else if ((*range_1)->end < (*range_2)->end)
1550 /* RANGE_1 and RANGE_2 intersect. */
1551 range_tmp = (*range_1)->end;
1552 (*range_1)->end = (*range_2)->start;
1553 (*range_2)->start = range_tmp;
1554 (*range_2)->end = (*range_2)->end;
1556 else /* (*range_1)->end == (*range_2)->end */
1558 /* RANGE_2 is a proper subset of RANGE_1. */
1559 (*range_1)->end = (*range_2)->start;
1560 *range_2 = NULL;
1564 is_compacted = TRUE;
1565 } /* ranges overlap */
1567 /* Return compacted ranges to their original direction. */
1568 if (*range_1 && range_1_is_reversed)
1569 range_swap_endpoints(*range_1);
1570 if (*range_2 && range_2_is_reversed)
1571 range_swap_endpoints(*range_2);
1572 return is_compacted;
1575 svn_merge_range_t *
1576 svn_merge_range_dup(svn_merge_range_t *range, apr_pool_t *pool)
1578 svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range));
1579 memcpy(new_range, range, sizeof(*new_range));
1580 return new_range;
1583 svn_boolean_t
1584 svn_merge_range_contains_rev(svn_merge_range_t *range, svn_revnum_t rev)
1586 assert(SVN_IS_VALID_REVNUM(range->start));
1587 assert(SVN_IS_VALID_REVNUM(range->end));
1588 assert(range->start != range->end);
1590 if (range->start < range->end)
1591 return rev > range->start && rev <= range->end;
1592 else
1593 return rev > range->end && rev <= range->start;