Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_subr / mergeinfo.c
blob4c93c58399d08dbca5736f82a9654582d70a27f5
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_string_t *. */
335 static svn_error_t *
336 range_to_string(svn_string_t **result, svn_merge_range_t *range,
337 apr_pool_t *pool)
339 if (range->start == range->end - 1)
340 *result = svn_string_createf(pool, "%ld%s", range->end,
341 range->inheritable
342 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
343 else
344 *result = svn_string_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_string_t *r1, *r2;
380 if ((*lastrange)->start <= mrange->end
381 && mrange->start <= (*lastrange)->end)
383 /* The ranges intersect. */
384 SVN_ERR(range_to_string(&r1, *lastrange, pool));
385 SVN_ERR(range_to_string(&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_string(&r1, *lastrange, pool));
406 SVN_ERR(range_to_string(&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, svn_mergeinfo_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, svn_mergeinfo_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(svn_mergeinfo_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 lasti = i;
760 elt1 = &wboardelt;
762 /* If the whiteboard range is contained completely in the
763 eraser, we increment the whiteboard.
764 If the ranges intersect, and match exactly, we increment both
765 eraser and whiteboard.
766 Otherwise, we have to generate a range for the left part of
767 the removal of eraser from whiteboard, and possibly change
768 the whiteboard to the remaining portion of the right part of
769 the removal, to test against. */
770 if (range_contains(elt2, elt1, consider_inheritance))
772 if (!do_remove)
773 combine_with_lastrange(&lastrange, elt1, TRUE, *output,
774 consider_inheritance, pool);
776 i++;
778 if (elt1->start == elt2->start && elt1->end == elt2->end)
779 j++;
781 else if (range_intersect(elt2, elt1, consider_inheritance))
783 if (elt1->start < elt2->start)
785 /* The whiteboard range starts before the eraser range. */
786 svn_merge_range_t tmp_range;
787 tmp_range.inheritable = elt1->inheritable;
788 if (do_remove)
790 /* Retain the range that falls before the eraser start. */
791 tmp_range.start = elt1->start;
792 tmp_range.end = elt2->start;
794 else
796 /* Retain the range that falls between the eraser
797 start and whiteboard end. */
798 tmp_range.start = elt2->start;
799 tmp_range.end = MIN(elt1->end, elt2->end);
802 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
803 *output, consider_inheritance, pool);
806 /* Set up the rest of the whiteboard range for further
807 processing. */
808 if (elt1->end > elt2->end)
810 /* The whiteboard range ends after the eraser range. */
811 if (!do_remove)
813 /* Partial overlap. */
814 svn_merge_range_t tmp_range;
815 tmp_range.start = MAX(elt1->start, elt2->start);
816 tmp_range.end = elt2->end;
817 tmp_range.inheritable = elt1->inheritable;
818 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
819 *output, consider_inheritance, pool);
822 wboardelt.start = elt2->end;
823 wboardelt.end = elt1->end;
825 else
826 i++;
828 else /* ranges don't intersect */
830 /* See which side of the whiteboard the eraser is on. If it
831 is on the left side, we need to move the eraser.
833 If it is on past the whiteboard on the right side, we
834 need to output the whiteboard and increment the
835 whiteboard. */
836 if (svn_sort_compare_ranges(&elt2, &elt1) < 0)
837 j++;
838 else
840 if (do_remove && !(lastrange &&
841 combine_ranges(&lastrange, lastrange, elt1,
842 consider_inheritance)))
844 lastrange = svn_merge_range_dup(elt1, pool);
845 APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange;
847 i++;
852 if (do_remove)
854 /* Copy the current whiteboard element if we didn't hit the end
855 of the whiteboard, and we still had it around. This element
856 may have been touched, so we can't just walk the whiteboard
857 array, we have to use our copy. This case only happens when
858 we ran out of eraser before whiteboard, *and* we had changed
859 the whiteboard element. */
860 if (i == lasti && i < whiteboard->nelts)
862 combine_with_lastrange(&lastrange, &wboardelt, TRUE, *output,
863 consider_inheritance, pool);
864 i++;
867 /* Copy any other remaining untouched whiteboard elements. */
868 for (; i < whiteboard->nelts; i++)
870 svn_merge_range_t *elt = APR_ARRAY_IDX(whiteboard, i,
871 svn_merge_range_t *);
873 combine_with_lastrange(&lastrange, elt, TRUE, *output,
874 consider_inheritance, pool);
878 return SVN_NO_ERROR;
882 /* Expected to handle all the range overlap cases: non, partial, full */
883 svn_error_t *
884 svn_rangelist_intersect(apr_array_header_t **output,
885 apr_array_header_t *rangelist1,
886 apr_array_header_t *rangelist2,
887 apr_pool_t *pool)
889 return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE,
890 TRUE, pool);
893 svn_error_t *
894 svn_rangelist_remove(apr_array_header_t **output,
895 apr_array_header_t *eraser,
896 apr_array_header_t *whiteboard,
897 svn_boolean_t consider_inheritance,
898 apr_pool_t *pool)
900 return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE,
901 consider_inheritance, pool);
904 /* Output deltas via *DELETED and *ADDED, which will never be @c NULL.
906 The following diagrams illustrate some common range delta scenarios:
908 (from) deleted
909 r0 <===========(=========)============[=========]===========> rHEAD
910 [to] added
912 (from) deleted deleted
913 r0 <===========(=========[============]=========)===========> rHEAD
914 [to]
916 (from) deleted
917 r0 <===========(=========[============)=========]===========> rHEAD
918 [to] added
920 (from) deleted
921 r0 <===========[=========(============]=========)===========> rHEAD
922 [to] added
924 (from)
925 r0 <===========[=========(============)=========]===========> rHEAD
926 [to] added added
928 (from) d d d
929 r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
930 [to] a a a a a a a
932 svn_error_t *
933 svn_rangelist_diff(apr_array_header_t **deleted, apr_array_header_t **added,
934 apr_array_header_t *from, apr_array_header_t *to,
935 svn_boolean_t consider_inheritance,
936 apr_pool_t *pool)
938 /* The items that are present in from, but not in to, must have been
939 deleted. */
940 SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance,
941 pool));
942 /* The items that are present in to, but not in from, must have been
943 added. */
944 SVN_ERR(svn_rangelist_remove(added, from, to, consider_inheritance, pool));
945 return SVN_NO_ERROR;
948 apr_uint64_t
949 svn_rangelist_count_revs(apr_array_header_t *rangelist)
951 apr_uint64_t nbr_revs = 0;
952 int i;
954 for (i = 0; i < rangelist->nelts; i++)
956 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
957 svn_merge_range_t *);
958 nbr_revs += range->end - range->start;
961 return nbr_revs;
964 /* Record deletions and additions of entire range lists (by path
965 presence), and delegate to svn_rangelist_diff() for delta
966 calculations on a specific path. */
967 /* ### TODO: Merge implementation with
968 ### libsvn_subr/sorts.c:svn_prop_diffs(). Factor out a generic
969 ### hash diffing function for addition to APR's apr_hash.h API. */
970 static svn_error_t *
971 walk_mergeinfo_hash_for_diff(svn_mergeinfo_t from, svn_mergeinfo_t to,
972 svn_mergeinfo_t deleted, svn_mergeinfo_t added,
973 svn_boolean_t consider_inheritance,
974 apr_pool_t *pool)
976 apr_hash_index_t *hi;
977 const void *key;
978 void *val;
979 const char *path;
980 apr_array_header_t *from_rangelist, *to_rangelist;
982 /* Handle path deletions and differences. */
983 for (hi = apr_hash_first(pool, from); hi; hi = apr_hash_next(hi))
985 apr_hash_this(hi, &key, NULL, &val);
986 path = key;
987 from_rangelist = val;
989 /* If the path is not present at all in the "to" hash, the
990 entire "from" rangelist is a deletion. Paths which are
991 present in the "to" hash require closer scrutiny. */
992 to_rangelist = apr_hash_get(to, path, APR_HASH_KEY_STRING);
993 if (to_rangelist)
995 /* Record any deltas (additions or deletions). */
996 apr_array_header_t *deleted_rangelist, *added_rangelist;
997 svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
998 from_rangelist, to_rangelist,
999 consider_inheritance, pool);
1000 if (deleted && deleted_rangelist->nelts > 0)
1001 apr_hash_set(deleted, apr_pstrdup(pool, path),
1002 APR_HASH_KEY_STRING, deleted_rangelist);
1003 if (added && added_rangelist->nelts > 0)
1004 apr_hash_set(added, apr_pstrdup(pool, path),
1005 APR_HASH_KEY_STRING, added_rangelist);
1007 else if (deleted)
1008 apr_hash_set(deleted, apr_pstrdup(pool, path), APR_HASH_KEY_STRING,
1009 svn_rangelist_dup(from_rangelist, pool));
1012 /* Handle path additions. */
1013 if (!added)
1014 return SVN_NO_ERROR;
1016 for (hi = apr_hash_first(pool, to); hi; hi = apr_hash_next(hi))
1018 apr_hash_this(hi, &key, NULL, &val);
1019 path = key;
1020 to_rangelist = val;
1022 /* If the path is not present in the "from" hash, the entire
1023 "to" rangelist is an addition. */
1024 if (apr_hash_get(from, path, APR_HASH_KEY_STRING) == NULL)
1025 apr_hash_set(added, apr_pstrdup(pool, path), APR_HASH_KEY_STRING,
1026 svn_rangelist_dup(to_rangelist, pool));
1029 return SVN_NO_ERROR;
1032 svn_error_t *
1033 svn_mergeinfo_diff(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
1034 svn_mergeinfo_t from, svn_mergeinfo_t to,
1035 svn_boolean_t consider_inheritance,
1036 apr_pool_t *pool)
1038 if (from && to == NULL)
1040 *deleted = svn_mergeinfo_dup(from, pool);
1041 *added = apr_hash_make(pool);
1043 else if (from == NULL && to)
1045 *deleted = apr_hash_make(pool);
1046 *added = svn_mergeinfo_dup(to, pool);
1048 else
1050 *deleted = apr_hash_make(pool);
1051 *added = apr_hash_make(pool);
1053 if (from && to)
1055 SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added,
1056 consider_inheritance, pool));
1060 return SVN_NO_ERROR;
1063 svn_error_t *
1064 svn_mergeinfo__equals(svn_boolean_t *is_equal,
1065 svn_mergeinfo_t info1,
1066 svn_mergeinfo_t info2,
1067 svn_boolean_t consider_inheritance,
1068 apr_pool_t *pool)
1070 if (apr_hash_count(info1) == apr_hash_count(info2))
1072 svn_mergeinfo_t deleted, added;
1073 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, info1, info2,
1074 consider_inheritance, pool));
1075 *is_equal = apr_hash_count(deleted) == 0 && apr_hash_count(added) == 0;
1077 else
1079 *is_equal = FALSE;
1081 return SVN_NO_ERROR;
1084 svn_error_t *
1085 svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo, svn_mergeinfo_t changes,
1086 apr_pool_t *pool)
1088 apr_array_header_t *sorted1, *sorted2;
1089 int i, j;
1091 sorted1 = svn_sort__hash(mergeinfo, svn_sort_compare_items_as_paths, pool);
1092 sorted2 = svn_sort__hash(changes, svn_sort_compare_items_as_paths, pool);
1094 i = 0;
1095 j = 0;
1096 while (i < sorted1->nelts && j < sorted2->nelts)
1098 svn_sort__item_t elt1, elt2;
1099 int res;
1101 elt1 = APR_ARRAY_IDX(sorted1, i, svn_sort__item_t);
1102 elt2 = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1103 res = svn_sort_compare_items_as_paths(&elt1, &elt2);
1105 if (res == 0)
1107 apr_array_header_t *rl1, *rl2;
1109 rl1 = elt1.value;
1110 rl2 = elt2.value;
1112 SVN_ERR(svn_rangelist_merge(&rl1, rl2,
1113 pool));
1114 apr_hash_set(mergeinfo, elt1.key, elt1.klen, rl1);
1115 i++;
1116 j++;
1118 else if (res < 0)
1120 i++;
1122 else
1124 apr_hash_set(mergeinfo, elt2.key, elt2.klen, elt2.value);
1125 j++;
1129 /* Copy back any remaining elements from the second hash. */
1130 for (; j < sorted2->nelts; j++)
1132 svn_sort__item_t elt = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1133 apr_hash_set(mergeinfo, elt.key, elt.klen, elt.value);
1136 return SVN_NO_ERROR;
1139 svn_error_t *
1140 svn_mergeinfo_intersect(svn_mergeinfo_t *mergeinfo,
1141 svn_mergeinfo_t mergeinfo1,
1142 svn_mergeinfo_t mergeinfo2,
1143 apr_pool_t *pool)
1145 apr_hash_index_t *hi;
1147 *mergeinfo = apr_hash_make(pool);
1149 /* ### TODO(reint): Do we care about the case when a path in one
1150 ### mergeinfo hash has inheritable mergeinfo, and in the other
1151 ### has non-inhertiable mergeinfo? It seems like that path
1152 ### itself should really be an intersection, while child paths
1153 ### should not be... */
1154 for (hi = apr_hash_first(apr_hash_pool_get(mergeinfo1), mergeinfo1);
1155 hi; hi = apr_hash_next(hi))
1157 apr_array_header_t *rangelist;
1158 const void *path;
1159 void *val;
1160 apr_hash_this(hi, &path, NULL, &val);
1162 rangelist = apr_hash_get(mergeinfo2, path, APR_HASH_KEY_STRING);
1163 if (rangelist)
1165 SVN_ERR(svn_rangelist_intersect(&rangelist,
1166 (apr_array_header_t *) val,
1167 rangelist, pool));
1168 if (rangelist->nelts > 0)
1169 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
1172 return SVN_NO_ERROR;
1175 svn_error_t *
1176 svn_mergeinfo_remove(svn_mergeinfo_t *mergeinfo, svn_mergeinfo_t eraser,
1177 svn_mergeinfo_t whiteboard, apr_pool_t *pool)
1179 *mergeinfo = apr_hash_make(pool);
1180 SVN_ERR(walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL,
1181 TRUE, pool));
1182 return SVN_NO_ERROR;
1185 svn_error_t *
1186 svn_rangelist_to_string(svn_string_t **output,
1187 const apr_array_header_t *rangelist,
1188 apr_pool_t *pool)
1190 svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
1192 if (rangelist->nelts > 0)
1194 int i;
1195 svn_merge_range_t *range;
1196 svn_string_t *toappend;
1198 /* Handle the elements that need commas at the end. */
1199 for (i = 0; i < rangelist->nelts - 1; i++)
1201 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1202 SVN_ERR(range_to_string(&toappend, range, pool));
1203 svn_stringbuf_appendcstr(buf, toappend->data);
1204 svn_stringbuf_appendcstr(buf, ",");
1207 /* Now handle the last element, which needs no comma. */
1208 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1209 SVN_ERR(range_to_string(&toappend, range, pool));
1210 svn_stringbuf_appendcstr(buf, toappend->data);
1213 *output = svn_string_create_from_buf(buf, pool);
1215 return SVN_NO_ERROR;
1218 /* Converts a mergeinfo @a input to an unparsed mergeinfo in @a
1219 * output. If @a input contains no elements, return the empty string.
1221 static svn_error_t *
1222 mergeinfo_to_stringbuf(svn_stringbuf_t **output, svn_mergeinfo_t input,
1223 apr_pool_t *pool)
1225 *output = svn_stringbuf_create("", pool);
1227 if (apr_hash_count(input) > 0)
1229 apr_array_header_t *sorted =
1230 svn_sort__hash(input, svn_sort_compare_items_as_paths, pool);
1231 int i;
1233 for (i = 0; i < sorted->nelts; i++)
1235 svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
1236 svn_string_t *revlist;
1238 SVN_ERR(svn_rangelist_to_string(&revlist, elt.value, pool));
1239 svn_stringbuf_appendcstr(*output,
1240 apr_psprintf(pool, "%s:%s",
1241 (char *) elt.key,
1242 revlist->data));
1243 if (i < sorted->nelts - 1)
1244 svn_stringbuf_appendcstr(*output, "\n");
1248 return SVN_NO_ERROR;
1251 svn_error_t *
1252 svn_mergeinfo_to_string(svn_string_t **output, svn_mergeinfo_t input,
1253 apr_pool_t *pool)
1255 if (apr_hash_count(input) > 0)
1257 svn_stringbuf_t *mergeinfo_buf;
1258 SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_buf, input, pool));
1259 *output = svn_string_create_from_buf(mergeinfo_buf, pool);
1261 else
1263 *output = svn_string_create("", pool);
1265 return SVN_NO_ERROR;
1268 /* Perform an in-place sort of the rangelists in a mergeinfo hash. */
1269 svn_error_t*
1270 svn_mergeinfo_sort(svn_mergeinfo_t input, apr_pool_t *pool)
1272 apr_hash_index_t *hi;
1273 void *val;
1275 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
1277 apr_array_header_t *rl;
1278 apr_hash_this(hi, NULL, NULL, &val);
1280 rl = val;
1281 qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges);
1283 return SVN_NO_ERROR;
1286 svn_mergeinfo_t
1287 svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool)
1289 svn_mergeinfo_t new_mergeinfo = apr_hash_make(pool);
1290 apr_hash_index_t *hi;
1291 const void *path;
1292 apr_ssize_t pathlen;
1293 void *rangelist;
1295 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1297 apr_hash_this(hi, &path, &pathlen, &rangelist);
1298 apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen,
1299 svn_rangelist_dup((apr_array_header_t *) rangelist, pool));
1302 return new_mergeinfo;
1305 svn_error_t *
1306 svn_mergeinfo_inheritable(svn_mergeinfo_t *output,
1307 svn_mergeinfo_t mergeinfo,
1308 const char *path,
1309 svn_revnum_t start,
1310 svn_revnum_t end,
1311 apr_pool_t *pool)
1313 apr_hash_index_t *hi;
1314 const void *key;
1315 apr_ssize_t keylen;
1316 void *rangelist;
1318 svn_mergeinfo_t inheritable_mergeinfo = apr_hash_make(pool);
1319 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1321 apr_array_header_t *inheritable_rangelist;
1322 apr_hash_this(hi, &key, &keylen, &rangelist);
1323 if (!path || svn_path_compare_paths(path, (const char *)key) == 0)
1324 SVN_ERR(svn_rangelist_inheritable(&inheritable_rangelist,
1325 (apr_array_header_t *) rangelist,
1326 start, end, pool));
1327 else
1328 inheritable_rangelist =
1329 svn_rangelist_dup((apr_array_header_t *)rangelist, pool);
1330 apr_hash_set(inheritable_mergeinfo,
1331 apr_pstrmemdup(pool, key, keylen), keylen,
1332 inheritable_rangelist);
1334 *output = inheritable_mergeinfo;
1335 return SVN_NO_ERROR;
1338 svn_error_t *
1339 svn_rangelist_inheritable(apr_array_header_t **inheritable_rangelist,
1340 apr_array_header_t *rangelist,
1341 svn_revnum_t start,
1342 svn_revnum_t end,
1343 apr_pool_t *pool)
1345 *inheritable_rangelist = apr_array_make(pool, 1,
1346 sizeof(svn_merge_range_t *));
1347 if (rangelist->nelts)
1349 if (!SVN_IS_VALID_REVNUM(start)
1350 || !SVN_IS_VALID_REVNUM(end)
1351 || end < start)
1353 int i;
1354 /* We want all non-inheritable ranges removed. */
1355 for (i = 0; i < rangelist->nelts; i++)
1357 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
1358 svn_merge_range_t *);
1359 if (range->inheritable)
1361 svn_merge_range_t *inheritable_range =
1362 apr_palloc(pool, sizeof(*inheritable_range));
1363 inheritable_range->start = range->start;
1364 inheritable_range->end = range->end;
1365 inheritable_range->inheritable = TRUE;
1366 APR_ARRAY_PUSH(*inheritable_rangelist,
1367 svn_merge_range_t *) = range;
1371 else
1373 /* We want only the non-inheritable ranges bound by START
1374 and END removed. */
1375 apr_array_header_t *ranges_inheritable =
1376 apr_array_make(pool, 0, sizeof(svn_merge_range_t *));
1377 svn_merge_range_t *range = apr_palloc(pool, sizeof(*range));
1379 range->start = start;
1380 range->end = end;
1381 range->inheritable = FALSE;
1382 APR_ARRAY_PUSH(ranges_inheritable, svn_merge_range_t *) = range;
1384 if (rangelist->nelts)
1385 SVN_ERR(svn_rangelist_remove(inheritable_rangelist,
1386 ranges_inheritable,
1387 rangelist,
1388 TRUE,
1389 pool));
1392 return SVN_NO_ERROR;
1395 svn_boolean_t
1396 svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo,
1397 apr_pool_t *pool)
1399 apr_hash_index_t *hi;
1400 svn_boolean_t removed_some_ranges = FALSE;
1402 if (mergeinfo)
1404 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1406 const void *key;
1407 void *value;
1408 const char *path;
1409 apr_array_header_t *rangelist;
1411 apr_hash_this(hi, &key, NULL, &value);
1412 path = key;
1413 rangelist = value;
1415 if (rangelist->nelts == 0)
1417 apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, NULL);
1418 removed_some_ranges = TRUE;
1422 return removed_some_ranges;
1425 svn_error_t *
1426 svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog,
1427 svn_mergeinfo_catalog_t in_catalog,
1428 const char *prefix,
1429 apr_pool_t *pool)
1431 apr_hash_index_t *hi;
1432 int prefix_len = strlen(prefix);
1434 *out_catalog = apr_hash_make(pool);
1436 for (hi = apr_hash_first(pool, in_catalog); hi; hi = apr_hash_next(hi))
1438 const void *key;
1439 const char *original_path;
1440 void *value;
1441 apr_ssize_t klen;
1443 apr_hash_this(hi, &key, &klen, &value);
1444 original_path = key;
1445 assert(klen >= prefix_len);
1446 assert(strncmp(key, prefix, prefix_len) == 0);
1448 apr_hash_set(*out_catalog, original_path + prefix_len, klen-prefix_len, value);
1451 return SVN_NO_ERROR;
1455 apr_array_header_t *
1456 svn_rangelist_dup(apr_array_header_t *rangelist, apr_pool_t *pool)
1458 apr_array_header_t *new_rl = apr_array_make(pool, rangelist->nelts,
1459 sizeof(svn_merge_range_t *));
1460 int i;
1462 for (i = 0; i < rangelist->nelts; i++)
1464 APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) =
1465 svn_merge_range_dup(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
1466 pool);
1469 return new_rl;
1472 svn_merge_range_t *
1473 svn_merge_range_dup(svn_merge_range_t *range, apr_pool_t *pool)
1475 svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range));
1476 memcpy(new_range, range, sizeof(*new_range));
1477 return new_range;
1480 svn_boolean_t
1481 svn_merge_range_contains_rev(svn_merge_range_t *range, svn_revnum_t rev)
1483 assert(SVN_IS_VALID_REVNUM(range->start));
1484 assert(SVN_IS_VALID_REVNUM(range->end));
1485 assert(range->start != range->end);
1487 if (range->start < range->end)
1488 return rev > range->start && rev <= range->end;
1489 else
1490 return rev > range->end && rev <= range->start;