Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_subr / mergeinfo.c
blob08845cc2887d0d89644066b29c5cf7f42a5fc22a
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"
32 #include "svn_hash.h"
34 /* Attempt to combine two adjacent or overlapping ranges, IN1 and IN2, and put
35 the result in OUTPUT. Return whether they could be combined.
37 CONSIDER_INHERITANCE determines how to account for the inheritability
38 of IN1 and IN2 when trying to combine ranges. If ranges with different
39 inheritability are combined (CONSIDER_INHERITANCE must be FALSE for this
40 to happen) the result is inheritable. If both ranges are inheritable the
41 result is inheritable. Only and if both ranges are non-inheritable is
42 the result is non-inheritable.
44 Range overlapping detection algorithm from
45 http://c2.com/cgi-bin/wiki/fullSearch?TestIfDateRangesOverlap
47 static svn_boolean_t
48 combine_ranges(svn_merge_range_t **output, svn_merge_range_t *in1,
49 svn_merge_range_t *in2,
50 svn_boolean_t consider_inheritance)
52 if (in1->start <= in2->end && in2->start <= in1->end)
54 if (!consider_inheritance
55 || (consider_inheritance
56 && ((in1->inheritable ? TRUE : FALSE)
57 == (in2->inheritable ? TRUE : FALSE))))
59 (*output)->start = MIN(in1->start, in2->start);
60 (*output)->end = MAX(in1->end, in2->end);
61 (*output)->inheritable =
62 (in1->inheritable || in2->inheritable) ? TRUE : FALSE;
63 return TRUE;
66 return FALSE;
69 /* pathname -> PATHNAME */
70 static svn_error_t *
71 parse_pathname(const char **input, const char *end,
72 svn_stringbuf_t **pathname, apr_pool_t *pool)
74 const char *curr = *input;
75 *pathname = svn_stringbuf_create("", pool);
77 while (curr < end && *curr != ':')
79 svn_stringbuf_appendbytes(*pathname, curr, 1);
80 curr++;
83 if ((*pathname)->len == 0)
84 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
85 _("No pathname preceeding ':'"));
86 *input = curr;
88 return SVN_NO_ERROR;
91 /* Helper for svn_rangelist_merge() and rangelist_intersect_or_remove().
93 If *LASTRANGE is not NULL it should point to the last element in REVLIST.
94 REVLIST must be sorted from lowest to highest revision and contain no
95 overlapping revision ranges. Any changes made to REVLIST will maintain
96 this guarantee.
98 If *LASTRANGE is NULL then push MRANGE to REVLIST.
100 If *LASTRANGE and MRANGE don't intersect then push MRANGE to REVLIST.
101 If they do intersect and have the same inheritability then combine the
102 ranges, updating *LASTRANGE to reflect the new combined range. If the
103 ranges intersect but differ in inheritability, then merge the ranges - see
104 the doc string for svn_mergeinfo_merge. This may result in a change to
105 *LASTRANGE's end field and the pushing of up to two new ranges on REVLIST.
107 e.g. *LASTRANGE: '4-10*' merged with MRANGE: '6'________
108 | | |
109 Update end field Push Account for trimmed
110 | | range from *LASTRANGE.
111 | | Push it last to
112 | | maintain sort order.
113 | | |
114 V V V
115 *LASTRANGE: '4-5*' MRANGE: '6' NEWRANGE: '6-10*'
117 Upon return, if any new ranges were pushed onto REVLIST, then set
118 *LASTRANGE to the last range pushed.
120 CONSIDER_INHERITANCE determines how to account for the inheritability of
121 MRANGE and *LASTRANGE when determining if they intersect. If
122 CONSIDER_INHERITANCE is TRUE, then only ranges with the same
123 inheritability can intersect and therefore be combined.
125 If DUP_MRANGE is TRUE then allocate a copy of MRANGE before pushing it
126 onto REVLIST.
128 static APR_INLINE void
129 combine_with_lastrange(svn_merge_range_t** lastrange,
130 svn_merge_range_t *mrange, svn_boolean_t dup_mrange,
131 apr_array_header_t *revlist,
132 svn_boolean_t consider_inheritance,
133 apr_pool_t *pool)
135 svn_merge_range_t *pushed_mrange_1 = NULL;
136 svn_merge_range_t *pushed_mrange_2 = NULL;
137 svn_boolean_t ranges_intersect = FALSE;
138 svn_boolean_t ranges_have_same_inheritance = FALSE;
140 if (*lastrange)
142 if ((*lastrange)->start <= mrange->end
143 && mrange->start <= (*lastrange)->end)
144 ranges_intersect = TRUE;
145 if ((*lastrange)->inheritable == mrange->inheritable)
146 ranges_have_same_inheritance = TRUE;
149 if (!(*lastrange)
150 || (!ranges_intersect || (!ranges_have_same_inheritance
151 && consider_inheritance)))
154 /* No *LASTRANGE
156 LASTRANGE and MRANGE don't intersect
158 LASTRANGE and MRANGE "intersect" but have different
159 inheritability and we are considering inheritance so
160 can't combined them...
162 ...In all these cases just push MRANGE onto *LASTRANGE. */
163 if (dup_mrange)
164 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
165 else
166 pushed_mrange_1 = mrange;
168 else /* MRANGE and *LASTRANGE intersect */
170 if (ranges_have_same_inheritance)
172 /* Intersecting ranges have the same inheritability
173 so just combine them. */
174 (*lastrange)->start = MIN((*lastrange)->start, mrange->start);
175 (*lastrange)->end = MAX((*lastrange)->end, mrange->end);
176 (*lastrange)->inheritable =
177 ((*lastrange)->inheritable || mrange->inheritable) ? TRUE : FALSE;
179 else /* Ranges intersect but have different
180 inheritability so merge the ranges. */
182 svn_revnum_t tmp_revnum;
184 /* Ranges have same starting revision. */
185 if ((*lastrange)->start == mrange->start)
187 if ((*lastrange)->end == mrange->end)
189 (*lastrange)->inheritable = TRUE;
191 else if ((*lastrange)->end > mrange->end)
193 if (!(*lastrange)->inheritable)
195 tmp_revnum = (*lastrange)->end;
196 (*lastrange)->end = mrange->end;
197 (*lastrange)->inheritable = TRUE;
198 if (dup_mrange)
199 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
200 else
201 pushed_mrange_1 = mrange;
202 pushed_mrange_1->start = pushed_mrange_1->start;
203 pushed_mrange_1->end = tmp_revnum;
204 *lastrange = pushed_mrange_1;
207 else /* (*lastrange)->end < mrange->end) */
209 if (mrange->inheritable)
211 (*lastrange)->inheritable = TRUE;
212 (*lastrange)->end = mrange->end;
214 else
216 if (dup_mrange)
217 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
218 else
219 pushed_mrange_1 = mrange;
220 pushed_mrange_1->start = (*lastrange)->end;
224 /* Ranges have same ending revision. (Same starting
225 and ending revisions already handled above.) */
226 else if ((*lastrange)->end == mrange->end)
228 if ((*lastrange)->start < mrange->start)
230 if (!(*lastrange)->inheritable)
232 (*lastrange)->end = mrange->start;
233 if (dup_mrange)
234 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
235 else
236 pushed_mrange_1 = mrange;
237 *lastrange = pushed_mrange_1;
240 else /* (*lastrange)->start > mrange->start */
242 (*lastrange)->start = mrange->start;
243 (*lastrange)->end = mrange->end;
244 (*lastrange)->inheritable = mrange->inheritable;
245 if (dup_mrange)
246 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
247 else
248 pushed_mrange_1 = mrange;
249 pushed_mrange_1->start = (*lastrange)->end;
250 pushed_mrange_1->inheritable = TRUE;
254 else /* Ranges have different starting and ending revisions. */
256 if ((*lastrange)->start < mrange->start)
258 /* If MRANGE is a proper subset of *LASTRANGE and
259 *LASTRANGE is inheritable there is nothing more
260 to do. */
261 if (!((*lastrange)->end > mrange->end
262 && (*lastrange)->inheritable))
264 tmp_revnum = (*lastrange)->end;
265 if (!(*lastrange)->inheritable)
266 (*lastrange)->end = mrange->start;
267 else
268 mrange->start = (*lastrange)->end;
269 if (dup_mrange)
270 pushed_mrange_1 = svn_merge_range_dup(mrange, pool);
271 else
272 pushed_mrange_1 = mrange;
274 if (tmp_revnum > mrange->end)
276 pushed_mrange_2 =
277 apr_palloc(pool, sizeof(*pushed_mrange_2));
278 pushed_mrange_2->start = mrange->end;
279 pushed_mrange_2->end = tmp_revnum;
280 pushed_mrange_2->inheritable =
281 (*lastrange)->inheritable;
283 mrange->inheritable = TRUE;
286 else /* ((*lastrange)->start > mrange->start) */
288 if ((*lastrange)->end < mrange->end)
290 pushed_mrange_2->start = (*lastrange)->end;
291 pushed_mrange_2->end = mrange->end;
292 pushed_mrange_2->inheritable = mrange->inheritable;
294 tmp_revnum = (*lastrange)->start;
295 (*lastrange)->start = mrange->start;
296 (*lastrange)->end = tmp_revnum;
297 (*lastrange)->inheritable = mrange->inheritable;
299 mrange->start = tmp_revnum;
300 mrange->end = pushed_mrange_2->start;
301 mrange->inheritable = TRUE;
303 else /* (*lastrange)->end > mrange->end */
305 pushed_mrange_2->start = mrange->end;
306 pushed_mrange_2->end = (*lastrange)->end;
307 pushed_mrange_2->inheritable =
308 (*lastrange)->inheritable;
310 tmp_revnum = (*lastrange)->start;
311 (*lastrange)->start = mrange->start;
312 (*lastrange)->end = tmp_revnum;
313 (*lastrange)->inheritable = mrange->inheritable;
315 mrange->start = tmp_revnum;
316 mrange->end = pushed_mrange_2->start;
317 mrange->inheritable = TRUE;
323 if (pushed_mrange_1)
325 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange_1;
326 *lastrange = pushed_mrange_1;
328 if (pushed_mrange_2)
330 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange_2;
331 *lastrange = pushed_mrange_2;
335 /* Convert a single svn_merge_range_t * back into an svn_string_t *. */
336 static svn_error_t *
337 range_to_string(svn_string_t **result, svn_merge_range_t *range,
338 apr_pool_t *pool)
340 if (range->start == range->end - 1)
341 *result = svn_string_createf(pool, "%ld%s", range->end,
342 range->inheritable
343 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
344 else if (range->start - 1 == range->end)
345 *result = svn_string_createf(pool, "-%ld%s", range->start,
346 range->inheritable
347 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
348 else if (range->start < range->end)
349 *result = svn_string_createf(pool, "%ld-%ld%s", range->start + 1,
350 range->end, range->inheritable
351 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
352 else
353 *result = svn_string_createf(pool, "%ld-%ld%s", range->start,
354 range->end + 1, range->inheritable
355 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR);
356 return SVN_NO_ERROR;
359 /* Helper for svn_mergeinfo_parse() via parse_revlist().
361 Similar to combine_with_lastrange() but enforces the some of the
362 restrictions noted in svn_mergeinfo_parse() on otherwise grammatically
363 correct rangelists, specifically the prohibitions on:
365 1) Overlapping revision ranges
367 2) Unordered revision ranges
369 Returns an SVN_ERR_MERGEINFO_PARSE_ERROR error if any of these rules
370 are violated. The restriction on revision ranges with a start revision
371 greater than or equal to its end revision is handled in parse_revlist().
373 Unlike combine_with_lastrange() this function *always* considers
374 inheritance, so only adjacent revision ranges with the same
375 inheritability are ever combined. */
376 static svn_error_t *
377 combine_with_adjacent_lastrange(svn_merge_range_t **lastrange,
378 svn_merge_range_t *mrange,
379 svn_boolean_t dup_mrange,
380 apr_array_header_t *revlist,
381 apr_pool_t *pool)
383 svn_merge_range_t *pushed_mrange = mrange;
385 if (*lastrange)
387 svn_string_t *r1, *r2;
389 if ((*lastrange)->start <= mrange->end
390 && mrange->start <= (*lastrange)->end)
392 /* The ranges intersect. */
393 SVN_ERR(range_to_string(&r1, *lastrange, pool));
394 SVN_ERR(range_to_string(&r2, mrange, pool));
396 /* svn_mergeinfo_parse promises to combine adjacent
397 ranges, but not overlapping ranges. */
398 if (mrange->start < (*lastrange)->end)
400 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
401 _("Parsing of overlapping revision "
402 "ranges '%s' and '%s' is not "
403 "supported"), r1->data, r2->data);
405 else if ((*lastrange)->inheritable == mrange->inheritable)
407 /* Combine adjacent ranges with the same inheritability. */
408 (*lastrange)->end = mrange->end;
409 return SVN_NO_ERROR;
412 else if ((*lastrange)->start > mrange->start)
414 SVN_ERR(range_to_string(&r1, *lastrange, pool));
415 SVN_ERR(range_to_string(&r2, mrange, pool));
416 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
417 _("Unable to parse unordered revision "
418 "ranges '%s' and '%s'"),
419 r1->data, r2->data);
423 if (dup_mrange)
424 pushed_mrange = svn_merge_range_dup(mrange, pool);
425 APR_ARRAY_PUSH(revlist, svn_merge_range_t *) = pushed_mrange;
426 *lastrange = pushed_mrange;
427 return SVN_NO_ERROR;
430 /* Helper for svn_mergeinfo_parse()
432 revisionlist -> (revisionelement)(COMMA revisionelement)*
433 revisionrange -> REVISION "-" REVISION("*")
434 revisionelement -> revisionrange | REVISION("*")
436 PATHNAME is the path this revisionlist is mapped to. It is
437 used only for producing a more descriptive error message.
439 static svn_error_t *
440 parse_revlist(const char **input, const char *end,
441 apr_array_header_t *revlist, const char *pathname,
442 apr_pool_t *pool)
444 const char *curr = *input;
445 svn_merge_range_t *lastrange = NULL;
447 /* Eat any leading horizontal white-space before the rangelist. */
448 while (curr < end && *curr != '\n' && isspace(*curr))
449 curr++;
451 if (*curr == '\n' || curr == end)
453 /* Empty range list. */
454 *input = curr;
455 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
456 _("Mergeinfo for '%s' maps to an "
457 "empty revision range"), pathname);
460 while (curr < end && *curr != '\n')
462 /* Parse individual revisions or revision ranges. */
463 svn_merge_range_t *mrange = apr_pcalloc(pool, sizeof(*mrange));
464 svn_revnum_t firstrev;
466 SVN_ERR(svn_revnum_parse(&firstrev, curr, &curr));
467 if (*curr != '-' && *curr != '\n' && *curr != ',' && *curr != '*'
468 && curr != end)
469 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
470 _("Invalid character '%c' found in revision "
471 "list"), *curr);
472 mrange->start = firstrev - 1;
473 mrange->end = firstrev;
474 mrange->inheritable = TRUE;
476 if (*curr == '-')
478 svn_revnum_t secondrev;
480 curr++;
481 SVN_ERR(svn_revnum_parse(&secondrev, curr, &curr));
482 if (firstrev > secondrev)
483 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
484 _("Unable to parse reversed revision "
485 "range '%ld-%ld'"),
486 firstrev, secondrev);
487 else if (firstrev == secondrev)
488 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
489 _("Unable to parse revision range "
490 "'%ld-%ld' with same start and end "
491 "revisions"), firstrev, secondrev);
492 mrange->end = secondrev;
495 if (*curr == '\n' || curr == end)
497 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange, FALSE,
498 revlist, pool));
499 *input = curr;
500 return SVN_NO_ERROR;
502 else if (*curr == ',')
504 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange, FALSE,
505 revlist, pool));
506 curr++;
508 else if (*curr == '*')
510 mrange->inheritable = FALSE;
511 curr++;
512 if (*curr == ',' || *curr == '\n' || curr == end)
514 SVN_ERR(combine_with_adjacent_lastrange(&lastrange, mrange,
515 FALSE, revlist, pool));
516 if (*curr == ',')
518 curr++;
520 else
522 *input = curr;
523 return SVN_NO_ERROR;
526 else
528 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
529 _("Invalid character '%c' found in "
530 "range list"), *curr);
533 else
535 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
536 _("Invalid character '%c' found in "
537 "range list"), *curr);
541 if (*curr != '\n')
542 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
543 _("Range list parsing ended before hitting "
544 "newline"));
545 *input = curr;
546 return SVN_NO_ERROR;
549 /* revisionline -> PATHNAME COLON revisionlist */
550 static svn_error_t *
551 parse_revision_line(const char **input, const char *end, svn_mergeinfo_t hash,
552 apr_pool_t *pool)
554 svn_stringbuf_t *pathname;
555 apr_array_header_t *revlist = apr_array_make(pool, 1,
556 sizeof(svn_merge_range_t *));
558 SVN_ERR(parse_pathname(input, end, &pathname, pool));
560 if (*(*input) != ':')
561 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
562 _("Pathname not terminated by ':'"));
564 *input = *input + 1;
566 SVN_ERR(parse_revlist(input, end, revlist, pathname->data, pool));
568 if (*input != end && *(*input) != '\n')
569 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
570 _("Could not find end of line in range list line "
571 "in '%s'"), *input);
573 if (*input != end)
574 *input = *input + 1;
576 qsort(revlist->elts, revlist->nelts, revlist->elt_size,
577 svn_sort_compare_ranges);
578 apr_hash_set(hash, pathname->data, APR_HASH_KEY_STRING, revlist);
580 return SVN_NO_ERROR;
583 /* top -> revisionline (NEWLINE revisionline)* */
584 static svn_error_t *
585 parse_top(const char **input, const char *end, svn_mergeinfo_t hash,
586 apr_pool_t *pool)
588 while (*input < end)
589 SVN_ERR(parse_revision_line(input, end, hash, pool));
591 return SVN_NO_ERROR;
594 svn_error_t *
595 svn_mergeinfo_parse(svn_mergeinfo_t *mergeinfo,
596 const char *input,
597 apr_pool_t *pool)
599 svn_error_t *err;
601 *mergeinfo = apr_hash_make(pool);
602 err = parse_top(&input, input + strlen(input), *mergeinfo, pool);
604 /* Always return SVN_ERR_MERGEINFO_PARSE_ERROR as the topmost error. */
605 if (err && err->apr_err != SVN_ERR_MERGEINFO_PARSE_ERROR)
606 err = svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR, err,
607 _("Could not parse mergeinfo string '%s'"),
608 input);
609 return err;
613 svn_error_t *
614 svn_rangelist_merge(apr_array_header_t **rangelist,
615 apr_array_header_t *changes,
616 apr_pool_t *pool)
618 int i, j;
619 svn_merge_range_t *lastrange = NULL;
620 apr_array_header_t *output = apr_array_make(pool, 1,
621 sizeof(svn_merge_range_t *));
622 i = 0;
623 j = 0;
624 while (i < (*rangelist)->nelts && j < changes->nelts)
626 svn_merge_range_t *elt1, *elt2;
627 int res;
629 elt1 = APR_ARRAY_IDX(*rangelist, i, svn_merge_range_t *);
630 elt2 = APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
632 res = svn_sort_compare_ranges(&elt1, &elt2);
633 if (res == 0)
635 /* Only when merging two non-inheritable ranges is the result also
636 non-inheritable. In all other cases ensure an inheritiable
637 result. */
638 if (elt1->inheritable || elt2->inheritable)
639 elt1->inheritable = TRUE;
640 combine_with_lastrange(&lastrange, elt1, TRUE, output,
641 FALSE, pool);
642 i++;
643 j++;
645 else if (res < 0)
647 combine_with_lastrange(&lastrange, elt1, TRUE, output,
648 FALSE, pool);
649 i++;
651 else
653 combine_with_lastrange(&lastrange, elt2, TRUE, output,
654 FALSE, pool);
655 j++;
658 /* Copy back any remaining elements.
659 Only one of these loops should end up running, if anything. */
661 assert (!(i < (*rangelist)->nelts && j < changes->nelts));
663 for (; i < (*rangelist)->nelts; i++)
665 svn_merge_range_t *elt = APR_ARRAY_IDX(*rangelist, i,
666 svn_merge_range_t *);
667 combine_with_lastrange(&lastrange, elt, TRUE, output,
668 FALSE, pool);
672 for (; j < changes->nelts; j++)
674 svn_merge_range_t *elt = APR_ARRAY_IDX(changes, j, svn_merge_range_t *);
675 combine_with_lastrange(&lastrange, elt, TRUE, output,
676 FALSE, pool);
679 *rangelist = output;
680 return SVN_NO_ERROR;
683 static svn_boolean_t
684 range_intersect(svn_merge_range_t *first, svn_merge_range_t *second,
685 svn_boolean_t consider_inheritance)
687 return (first->start + 1 <= second->end)
688 && (second->start + 1 <= first->end)
689 && (!consider_inheritance
690 || (!(first->inheritable) == !(second->inheritable)));
693 static svn_boolean_t
694 range_contains(svn_merge_range_t *first, svn_merge_range_t *second,
695 svn_boolean_t consider_inheritance)
697 return (first->start <= second->start) && (second->end <= first->end)
698 && (!consider_inheritance
699 || (!(first->inheritable) == !(second->inheritable)));
702 /* Swap start and end fields of RANGE. */
703 static void
704 range_swap_endpoints(svn_merge_range_t *range)
706 svn_revnum_t swap = range->start;
707 range->start = range->end;
708 range->end = swap;
711 svn_error_t *
712 svn_rangelist_reverse(apr_array_header_t *rangelist, apr_pool_t *pool)
714 int i, swap_index;
715 svn_merge_range_t range;
716 for (i = 0; i < rangelist->nelts / 2; i++)
718 swap_index = rangelist->nelts - i - 1;
719 range = *APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
720 *APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *) =
721 *APR_ARRAY_IDX(rangelist, swap_index, svn_merge_range_t *);
722 *APR_ARRAY_IDX(rangelist, swap_index, svn_merge_range_t *) = range;
723 range_swap_endpoints(APR_ARRAY_IDX(rangelist, swap_index,
724 svn_merge_range_t *));
725 range_swap_endpoints(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *));
728 /* If there's an odd number of elements, we still need to swap the
729 end points of the remaining range. */
730 if (rangelist->nelts % 2 == 1)
731 range_swap_endpoints(APR_ARRAY_IDX(rangelist, rangelist->nelts / 2,
732 svn_merge_range_t *));
734 return SVN_NO_ERROR;
737 /* Either remove any overlapping ranges described by ERASER from
738 WHITEBOARD (when DO_REMOVE is TRUE), or capture the overlap, and
739 place the remaining or overlapping ranges in OUTPUT. */
740 /* ### FIXME: Some variables names and inline comments for this method
741 ### are legacy from when it was solely the remove() impl. */
742 static svn_error_t *
743 rangelist_intersect_or_remove(apr_array_header_t **output,
744 apr_array_header_t *eraser,
745 apr_array_header_t *whiteboard,
746 svn_boolean_t do_remove,
747 svn_boolean_t consider_inheritance,
748 apr_pool_t *pool)
750 int i, j, lasti;
751 svn_merge_range_t *lastrange = NULL;
752 svn_merge_range_t wboardelt;
754 *output = apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
756 i = 0;
757 j = 0;
758 lasti = -1; /* Initialized to a value that "i" will never be. */
760 while (i < whiteboard->nelts && j < eraser->nelts)
762 svn_merge_range_t *elt1, *elt2;
764 elt2 = APR_ARRAY_IDX(eraser, j, svn_merge_range_t *);
766 /* Instead of making a copy of the entire array of whiteboard
767 elements, we just keep a copy of the current whiteboard element
768 that needs to be used, and modify our copy if necessary. */
769 if (i != lasti)
771 wboardelt = *(APR_ARRAY_IDX(whiteboard, i, svn_merge_range_t *));
772 lasti = i;
775 elt1 = &wboardelt;
777 /* If the whiteboard range is contained completely in the
778 eraser, we increment the whiteboard.
779 If the ranges intersect, and match exactly, we increment both
780 eraser and whiteboard.
781 Otherwise, we have to generate a range for the left part of
782 the removal of eraser from whiteboard, and possibly change
783 the whiteboard to the remaining portion of the right part of
784 the removal, to test against. */
785 if (range_contains(elt2, elt1, consider_inheritance))
787 if (!do_remove)
788 combine_with_lastrange(&lastrange, elt1, TRUE, *output,
789 consider_inheritance, pool);
791 i++;
793 if (elt1->start == elt2->start && elt1->end == elt2->end)
794 j++;
796 else if (range_intersect(elt2, elt1, consider_inheritance))
798 if (elt1->start < elt2->start)
800 /* The whiteboard range starts before the eraser range. */
801 svn_merge_range_t tmp_range;
802 tmp_range.inheritable = elt1->inheritable;
803 if (do_remove)
805 /* Retain the range that falls before the eraser start. */
806 tmp_range.start = elt1->start;
807 tmp_range.end = elt2->start;
809 else
811 /* Retain the range that falls between the eraser
812 start and whiteboard end. */
813 tmp_range.start = elt2->start;
814 tmp_range.end = MIN(elt1->end, elt2->end);
817 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
818 *output, consider_inheritance, pool);
821 /* Set up the rest of the whiteboard range for further
822 processing. */
823 if (elt1->end > elt2->end)
825 /* The whiteboard range ends after the eraser range. */
826 if (!do_remove)
828 /* Partial overlap. */
829 svn_merge_range_t tmp_range;
830 tmp_range.start = MAX(elt1->start, elt2->start);
831 tmp_range.end = elt2->end;
832 tmp_range.inheritable = elt1->inheritable;
833 combine_with_lastrange(&lastrange, &tmp_range, TRUE,
834 *output, consider_inheritance, pool);
837 wboardelt.start = elt2->end;
838 wboardelt.end = elt1->end;
840 else
841 i++;
843 else /* ranges don't intersect */
845 /* See which side of the whiteboard the eraser is on. If it
846 is on the left side, we need to move the eraser.
848 If it is on past the whiteboard on the right side, we
849 need to output the whiteboard and increment the
850 whiteboard. */
851 if (svn_sort_compare_ranges(&elt2, &elt1) < 0)
852 j++;
853 else
855 if (do_remove && !(lastrange &&
856 combine_ranges(&lastrange, lastrange, elt1,
857 consider_inheritance)))
859 lastrange = svn_merge_range_dup(elt1, pool);
860 APR_ARRAY_PUSH(*output, svn_merge_range_t *) = lastrange;
862 i++;
867 if (do_remove)
869 /* Copy the current whiteboard element if we didn't hit the end
870 of the whiteboard, and we still had it around. This element
871 may have been touched, so we can't just walk the whiteboard
872 array, we have to use our copy. This case only happens when
873 we ran out of eraser before whiteboard, *and* we had changed
874 the whiteboard element. */
875 if (i == lasti && i < whiteboard->nelts)
877 combine_with_lastrange(&lastrange, &wboardelt, TRUE, *output,
878 consider_inheritance, pool);
879 i++;
882 /* Copy any other remaining untouched whiteboard elements. */
883 for (; i < whiteboard->nelts; i++)
885 svn_merge_range_t *elt = APR_ARRAY_IDX(whiteboard, i,
886 svn_merge_range_t *);
888 combine_with_lastrange(&lastrange, elt, TRUE, *output,
889 consider_inheritance, pool);
893 return SVN_NO_ERROR;
897 svn_error_t *
898 svn_rangelist_intersect(apr_array_header_t **output,
899 apr_array_header_t *rangelist1,
900 apr_array_header_t *rangelist2,
901 apr_pool_t *pool)
903 return rangelist_intersect_or_remove(output, rangelist1, rangelist2, FALSE,
904 TRUE, pool);
907 svn_error_t *
908 svn_rangelist_remove(apr_array_header_t **output,
909 apr_array_header_t *eraser,
910 apr_array_header_t *whiteboard,
911 svn_boolean_t consider_inheritance,
912 apr_pool_t *pool)
914 return rangelist_intersect_or_remove(output, eraser, whiteboard, TRUE,
915 consider_inheritance, pool);
918 svn_error_t *
919 svn_rangelist_diff(apr_array_header_t **deleted, apr_array_header_t **added,
920 apr_array_header_t *from, apr_array_header_t *to,
921 svn_boolean_t consider_inheritance,
922 apr_pool_t *pool)
924 /* The following diagrams illustrate some common range delta scenarios:
926 (from) deleted
927 r0 <===========(=========)============[=========]===========> rHEAD
928 [to] added
930 (from) deleted deleted
931 r0 <===========(=========[============]=========)===========> rHEAD
932 [to]
934 (from) deleted
935 r0 <===========(=========[============)=========]===========> rHEAD
936 [to] added
938 (from) deleted
939 r0 <===========[=========(============]=========)===========> rHEAD
940 [to] added
942 (from)
943 r0 <===========[=========(============)=========]===========> rHEAD
944 [to] added added
946 (from) d d d
947 r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
948 [to] a a a a a a a
951 /* The items that are present in from, but not in to, must have been
952 deleted. */
953 SVN_ERR(svn_rangelist_remove(deleted, to, from, consider_inheritance,
954 pool));
955 /* The items that are present in to, but not in from, must have been
956 added. */
957 SVN_ERR(svn_rangelist_remove(added, from, to, consider_inheritance, pool));
958 return SVN_NO_ERROR;
961 struct mergeinfo_diff_baton
963 svn_mergeinfo_t from;
964 svn_mergeinfo_t to;
965 svn_mergeinfo_t deleted;
966 svn_mergeinfo_t added;
967 svn_boolean_t consider_inheritance;
968 apr_pool_t *pool;
971 /* This implements the 'svn_hash_diff_func_t' interface.
972 BATON is of type 'struct mergeinfo_diff_baton *'.
974 static svn_error_t *
975 mergeinfo_hash_diff_cb(const void *key, apr_ssize_t klen,
976 enum svn_hash_diff_key_status status,
977 void *baton)
979 /* hash_a is FROM mergeinfo,
980 hash_b is TO mergeinfo. */
981 struct mergeinfo_diff_baton *cb = baton;
982 apr_array_header_t *from_rangelist, *to_rangelist;
983 const char *path = key;
984 if (status == svn_hash_diff_key_both)
986 /* Record any deltas (additions or deletions). */
987 apr_array_header_t *deleted_rangelist, *added_rangelist;
988 from_rangelist = apr_hash_get(cb->from, path, APR_HASH_KEY_STRING);
989 to_rangelist = apr_hash_get(cb->to, path, APR_HASH_KEY_STRING);
990 svn_rangelist_diff(&deleted_rangelist, &added_rangelist,
991 from_rangelist, to_rangelist,
992 cb->consider_inheritance, cb->pool);
993 if (cb->deleted && deleted_rangelist->nelts > 0)
994 apr_hash_set(cb->deleted, apr_pstrdup(cb->pool, path),
995 APR_HASH_KEY_STRING, deleted_rangelist);
996 if (cb->added && added_rangelist->nelts > 0)
997 apr_hash_set(cb->added, apr_pstrdup(cb->pool, path),
998 APR_HASH_KEY_STRING, added_rangelist);
1000 else if ((status == svn_hash_diff_key_a) && cb->deleted)
1002 from_rangelist = apr_hash_get(cb->from, path, APR_HASH_KEY_STRING);
1003 apr_hash_set(cb->deleted, apr_pstrdup(cb->pool, path),
1004 APR_HASH_KEY_STRING,
1005 svn_rangelist_dup(from_rangelist, cb->pool));
1007 else if ((status == svn_hash_diff_key_b) && cb->added)
1009 to_rangelist = apr_hash_get(cb->to, path, APR_HASH_KEY_STRING);
1010 apr_hash_set(cb->added, apr_pstrdup(cb->pool, path), APR_HASH_KEY_STRING,
1011 svn_rangelist_dup(to_rangelist, cb->pool));
1013 return SVN_NO_ERROR;
1016 /* Record deletions and additions of entire range lists (by path
1017 presence), and delegate to svn_rangelist_diff() for delta
1018 calculations on a specific path. */
1019 static svn_error_t *
1020 walk_mergeinfo_hash_for_diff(svn_mergeinfo_t from, svn_mergeinfo_t to,
1021 svn_mergeinfo_t deleted, svn_mergeinfo_t added,
1022 svn_boolean_t consider_inheritance,
1023 apr_pool_t *pool)
1025 struct mergeinfo_diff_baton mdb;
1026 mdb.from = from;
1027 mdb.to = to;
1028 mdb.deleted = deleted;
1029 mdb.added = added;
1030 mdb.consider_inheritance = consider_inheritance;
1031 mdb.pool = pool;
1033 SVN_ERR(svn_hash_diff(from, to, mergeinfo_hash_diff_cb, &mdb, pool));
1035 return SVN_NO_ERROR;
1038 svn_error_t *
1039 svn_mergeinfo_diff(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
1040 svn_mergeinfo_t from, svn_mergeinfo_t to,
1041 svn_boolean_t consider_inheritance,
1042 apr_pool_t *pool)
1044 if (from && to == NULL)
1046 *deleted = svn_mergeinfo_dup(from, pool);
1047 *added = apr_hash_make(pool);
1049 else if (from == NULL && to)
1051 *deleted = apr_hash_make(pool);
1052 *added = svn_mergeinfo_dup(to, pool);
1054 else
1056 *deleted = apr_hash_make(pool);
1057 *added = apr_hash_make(pool);
1059 if (from && to)
1061 SVN_ERR(walk_mergeinfo_hash_for_diff(from, to, *deleted, *added,
1062 consider_inheritance, pool));
1066 return SVN_NO_ERROR;
1069 svn_error_t *
1070 svn_mergeinfo__equals(svn_boolean_t *is_equal,
1071 svn_mergeinfo_t info1,
1072 svn_mergeinfo_t info2,
1073 svn_boolean_t consider_inheritance,
1074 apr_pool_t *pool)
1076 if (apr_hash_count(info1) == apr_hash_count(info2))
1078 svn_mergeinfo_t deleted, added;
1079 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, info1, info2,
1080 consider_inheritance, pool));
1081 *is_equal = apr_hash_count(deleted) == 0 && apr_hash_count(added) == 0;
1083 else
1085 *is_equal = FALSE;
1087 return SVN_NO_ERROR;
1090 svn_error_t *
1091 svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo, svn_mergeinfo_t changes,
1092 apr_pool_t *pool)
1094 apr_array_header_t *sorted1, *sorted2;
1095 int i, j;
1097 sorted1 = svn_sort__hash(mergeinfo, svn_sort_compare_items_as_paths, pool);
1098 sorted2 = svn_sort__hash(changes, svn_sort_compare_items_as_paths, pool);
1100 i = 0;
1101 j = 0;
1102 while (i < sorted1->nelts && j < sorted2->nelts)
1104 svn_sort__item_t elt1, elt2;
1105 int res;
1107 elt1 = APR_ARRAY_IDX(sorted1, i, svn_sort__item_t);
1108 elt2 = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1109 res = svn_sort_compare_items_as_paths(&elt1, &elt2);
1111 if (res == 0)
1113 apr_array_header_t *rl1, *rl2;
1115 rl1 = elt1.value;
1116 rl2 = elt2.value;
1118 SVN_ERR(svn_rangelist_merge(&rl1, rl2,
1119 pool));
1120 apr_hash_set(mergeinfo, elt1.key, elt1.klen, rl1);
1121 i++;
1122 j++;
1124 else if (res < 0)
1126 i++;
1128 else
1130 apr_hash_set(mergeinfo, elt2.key, elt2.klen, elt2.value);
1131 j++;
1135 /* Copy back any remaining elements from the second hash. */
1136 for (; j < sorted2->nelts; j++)
1138 svn_sort__item_t elt = APR_ARRAY_IDX(sorted2, j, svn_sort__item_t);
1139 apr_hash_set(mergeinfo, elt.key, elt.klen, elt.value);
1142 return SVN_NO_ERROR;
1145 svn_error_t *
1146 svn_mergeinfo_intersect(svn_mergeinfo_t *mergeinfo,
1147 svn_mergeinfo_t mergeinfo1,
1148 svn_mergeinfo_t mergeinfo2,
1149 apr_pool_t *pool)
1151 apr_hash_index_t *hi;
1153 *mergeinfo = apr_hash_make(pool);
1155 /* ### TODO(reint): Do we care about the case when a path in one
1156 ### mergeinfo hash has inheritable mergeinfo, and in the other
1157 ### has non-inhertiable mergeinfo? It seems like that path
1158 ### itself should really be an intersection, while child paths
1159 ### should not be... */
1160 for (hi = apr_hash_first(apr_hash_pool_get(mergeinfo1), mergeinfo1);
1161 hi; hi = apr_hash_next(hi))
1163 apr_array_header_t *rangelist;
1164 const void *path;
1165 void *val;
1166 apr_hash_this(hi, &path, NULL, &val);
1168 rangelist = apr_hash_get(mergeinfo2, path, APR_HASH_KEY_STRING);
1169 if (rangelist)
1171 SVN_ERR(svn_rangelist_intersect(&rangelist,
1172 (apr_array_header_t *) val,
1173 rangelist, pool));
1174 if (rangelist->nelts > 0)
1175 apr_hash_set(*mergeinfo, path, APR_HASH_KEY_STRING, rangelist);
1178 return SVN_NO_ERROR;
1181 svn_error_t *
1182 svn_mergeinfo_remove(svn_mergeinfo_t *mergeinfo, svn_mergeinfo_t eraser,
1183 svn_mergeinfo_t whiteboard, apr_pool_t *pool)
1185 *mergeinfo = apr_hash_make(pool);
1186 SVN_ERR(walk_mergeinfo_hash_for_diff(whiteboard, eraser, *mergeinfo, NULL,
1187 TRUE, pool));
1188 return SVN_NO_ERROR;
1191 svn_error_t *
1192 svn_rangelist_to_string(svn_string_t **output,
1193 const apr_array_header_t *rangelist,
1194 apr_pool_t *pool)
1196 svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
1198 if (rangelist->nelts > 0)
1200 int i;
1201 svn_merge_range_t *range;
1202 svn_string_t *toappend;
1204 /* Handle the elements that need commas at the end. */
1205 for (i = 0; i < rangelist->nelts - 1; i++)
1207 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1208 SVN_ERR(range_to_string(&toappend, range, pool));
1209 svn_stringbuf_appendcstr(buf, toappend->data);
1210 svn_stringbuf_appendcstr(buf, ",");
1213 /* Now handle the last element, which needs no comma. */
1214 range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
1215 SVN_ERR(range_to_string(&toappend, range, pool));
1216 svn_stringbuf_appendcstr(buf, toappend->data);
1219 *output = svn_string_create_from_buf(buf, pool);
1221 return SVN_NO_ERROR;
1224 /* Converts a mergeinfo @a input to an unparsed mergeinfo in @a
1225 * output. If @a input contains no elements, return the empty string.
1227 static svn_error_t *
1228 mergeinfo_to_stringbuf(svn_stringbuf_t **output, svn_mergeinfo_t input,
1229 apr_pool_t *pool)
1231 *output = svn_stringbuf_create("", pool);
1233 if (apr_hash_count(input) > 0)
1235 apr_array_header_t *sorted =
1236 svn_sort__hash(input, svn_sort_compare_items_as_paths, pool);
1237 int i;
1239 for (i = 0; i < sorted->nelts; i++)
1241 svn_sort__item_t elt = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
1242 svn_string_t *revlist;
1244 SVN_ERR(svn_rangelist_to_string(&revlist, elt.value, pool));
1245 svn_stringbuf_appendcstr(*output,
1246 apr_psprintf(pool, "%s:%s",
1247 (char *) elt.key,
1248 revlist->data));
1249 if (i < sorted->nelts - 1)
1250 svn_stringbuf_appendcstr(*output, "\n");
1254 return SVN_NO_ERROR;
1257 svn_error_t *
1258 svn_mergeinfo_to_string(svn_string_t **output, svn_mergeinfo_t input,
1259 apr_pool_t *pool)
1261 if (apr_hash_count(input) > 0)
1263 svn_stringbuf_t *mergeinfo_buf;
1264 SVN_ERR(mergeinfo_to_stringbuf(&mergeinfo_buf, input, pool));
1265 *output = svn_string_create_from_buf(mergeinfo_buf, pool);
1267 else
1269 *output = svn_string_create("", pool);
1271 return SVN_NO_ERROR;
1274 svn_error_t *
1275 svn_mergeinfo_sort(svn_mergeinfo_t input, apr_pool_t *pool)
1277 apr_hash_index_t *hi;
1278 void *val;
1280 for (hi = apr_hash_first(pool, input); hi; hi = apr_hash_next(hi))
1282 apr_array_header_t *rl;
1283 apr_hash_this(hi, NULL, NULL, &val);
1285 rl = val;
1286 qsort(rl->elts, rl->nelts, rl->elt_size, svn_sort_compare_ranges);
1288 return SVN_NO_ERROR;
1291 svn_mergeinfo_t
1292 svn_mergeinfo_dup(svn_mergeinfo_t mergeinfo, apr_pool_t *pool)
1294 svn_mergeinfo_t new_mergeinfo = apr_hash_make(pool);
1295 apr_hash_index_t *hi;
1296 const void *path;
1297 apr_ssize_t pathlen;
1298 void *rangelist;
1300 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1302 apr_hash_this(hi, &path, &pathlen, &rangelist);
1303 apr_hash_set(new_mergeinfo, apr_pstrmemdup(pool, path, pathlen), pathlen,
1304 svn_rangelist_dup((apr_array_header_t *) rangelist, pool));
1307 return new_mergeinfo;
1310 svn_error_t *
1311 svn_mergeinfo_inheritable(svn_mergeinfo_t *output,
1312 svn_mergeinfo_t mergeinfo,
1313 const char *path,
1314 svn_revnum_t start,
1315 svn_revnum_t end,
1316 apr_pool_t *pool)
1318 apr_hash_index_t *hi;
1319 const void *key;
1320 apr_ssize_t keylen;
1321 void *rangelist;
1323 svn_mergeinfo_t inheritable_mergeinfo = apr_hash_make(pool);
1324 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1326 apr_array_header_t *inheritable_rangelist;
1327 apr_hash_this(hi, &key, &keylen, &rangelist);
1328 if (!path || svn_path_compare_paths(path, (const char *)key) == 0)
1329 SVN_ERR(svn_rangelist_inheritable(&inheritable_rangelist,
1330 (apr_array_header_t *) rangelist,
1331 start, end, pool));
1332 else
1333 inheritable_rangelist =
1334 svn_rangelist_dup((apr_array_header_t *)rangelist, pool);
1335 apr_hash_set(inheritable_mergeinfo,
1336 apr_pstrmemdup(pool, key, keylen), keylen,
1337 inheritable_rangelist);
1339 *output = inheritable_mergeinfo;
1340 return SVN_NO_ERROR;
1343 svn_error_t *
1344 svn_rangelist_inheritable(apr_array_header_t **inheritable_rangelist,
1345 apr_array_header_t *rangelist,
1346 svn_revnum_t start,
1347 svn_revnum_t end,
1348 apr_pool_t *pool)
1350 *inheritable_rangelist = apr_array_make(pool, 1,
1351 sizeof(svn_merge_range_t *));
1352 if (rangelist->nelts)
1354 if (!SVN_IS_VALID_REVNUM(start)
1355 || !SVN_IS_VALID_REVNUM(end)
1356 || end < start)
1358 int i;
1359 /* We want all non-inheritable ranges removed. */
1360 for (i = 0; i < rangelist->nelts; i++)
1362 svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i,
1363 svn_merge_range_t *);
1364 if (range->inheritable)
1366 svn_merge_range_t *inheritable_range =
1367 apr_palloc(pool, sizeof(*inheritable_range));
1368 inheritable_range->start = range->start;
1369 inheritable_range->end = range->end;
1370 inheritable_range->inheritable = TRUE;
1371 APR_ARRAY_PUSH(*inheritable_rangelist,
1372 svn_merge_range_t *) = range;
1376 else
1378 /* We want only the non-inheritable ranges bound by START
1379 and END removed. */
1380 apr_array_header_t *ranges_inheritable =
1381 apr_array_make(pool, 0, sizeof(svn_merge_range_t *));
1382 svn_merge_range_t *range = apr_palloc(pool, sizeof(*range));
1384 range->start = start;
1385 range->end = end;
1386 range->inheritable = FALSE;
1387 APR_ARRAY_PUSH(ranges_inheritable, svn_merge_range_t *) = range;
1389 if (rangelist->nelts)
1390 SVN_ERR(svn_rangelist_remove(inheritable_rangelist,
1391 ranges_inheritable,
1392 rangelist,
1393 TRUE,
1394 pool));
1397 return SVN_NO_ERROR;
1400 svn_boolean_t
1401 svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo,
1402 apr_pool_t *pool)
1404 apr_hash_index_t *hi;
1405 svn_boolean_t removed_some_ranges = FALSE;
1407 if (mergeinfo)
1409 for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi))
1411 const void *key;
1412 void *value;
1413 const char *path;
1414 apr_array_header_t *rangelist;
1416 apr_hash_this(hi, &key, NULL, &value);
1417 path = key;
1418 rangelist = value;
1420 if (rangelist->nelts == 0)
1422 apr_hash_set(mergeinfo, path, APR_HASH_KEY_STRING, NULL);
1423 removed_some_ranges = TRUE;
1427 return removed_some_ranges;
1430 svn_error_t *
1431 svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t *out_catalog,
1432 svn_mergeinfo_catalog_t in_catalog,
1433 const char *prefix,
1434 apr_pool_t *pool)
1436 apr_hash_index_t *hi;
1437 int prefix_len = strlen(prefix);
1439 *out_catalog = apr_hash_make(pool);
1441 for (hi = apr_hash_first(pool, in_catalog); hi; hi = apr_hash_next(hi))
1443 const void *key;
1444 const char *original_path;
1445 void *value;
1446 apr_ssize_t klen;
1448 apr_hash_this(hi, &key, &klen, &value);
1449 original_path = key;
1450 assert(klen >= prefix_len);
1451 assert(strncmp(key, prefix, prefix_len) == 0);
1453 apr_hash_set(*out_catalog, original_path + prefix_len, klen-prefix_len, value);
1456 return SVN_NO_ERROR;
1460 apr_array_header_t *
1461 svn_rangelist_dup(apr_array_header_t *rangelist, apr_pool_t *pool)
1463 apr_array_header_t *new_rl = apr_array_make(pool, rangelist->nelts,
1464 sizeof(svn_merge_range_t *));
1465 int i;
1467 for (i = 0; i < rangelist->nelts; i++)
1469 APR_ARRAY_PUSH(new_rl, svn_merge_range_t *) =
1470 svn_merge_range_dup(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *),
1471 pool);
1474 return new_rl;
1477 svn_merge_range_t *
1478 svn_merge_range_dup(svn_merge_range_t *range, apr_pool_t *pool)
1480 svn_merge_range_t *new_range = apr_palloc(pool, sizeof(*new_range));
1481 memcpy(new_range, range, sizeof(*new_range));
1482 return new_range;
1485 svn_boolean_t
1486 svn_merge_range_contains_rev(svn_merge_range_t *range, svn_revnum_t rev)
1488 assert(SVN_IS_VALID_REVNUM(range->start));
1489 assert(SVN_IS_VALID_REVNUM(range->end));
1490 assert(range->start != range->end);
1492 if (range->start < range->end)
1493 return rev > range->start && rev <= range->end;
1494 else
1495 return rev > range->end && rev <= range->start;