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 * ====================================================================
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"
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
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
;
69 /* pathname -> PATHNAME */
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);
83 if ((*pathname
)->len
== 0)
84 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
85 _("No pathname preceeding ':'"));
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
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'________
109 Update end field Push Account for trimmed
110 | | range from *LASTRANGE.
112 | | maintain sort order.
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
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
,
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
;
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
;
150 || (!ranges_intersect
|| (!ranges_have_same_inheritance
151 && consider_inheritance
)))
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. */
164 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
199 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
217 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
234 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
246 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
261 if (!((*lastrange
)->end
> mrange
->end
262 && (*lastrange
)->inheritable
))
264 tmp_revnum
= (*lastrange
)->end
;
265 if (!(*lastrange
)->inheritable
)
266 (*lastrange
)->end
= mrange
->start
;
268 mrange
->start
= (*lastrange
)->end
;
270 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
272 pushed_mrange_1
= mrange
;
274 if (tmp_revnum
> mrange
->end
)
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
;
325 APR_ARRAY_PUSH(revlist
, svn_merge_range_t
*) = pushed_mrange_1
;
326 *lastrange
= pushed_mrange_1
;
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 *. */
337 range_to_string(svn_string_t
**result
, svn_merge_range_t
*range
,
340 if (range
->start
== range
->end
- 1)
341 *result
= svn_string_createf(pool
, "%ld%s", range
->end
,
343 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR
);
344 else if (range
->start
- 1 == range
->end
)
345 *result
= svn_string_createf(pool
, "-%ld%s", range
->start
,
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
);
353 *result
= svn_string_createf(pool
, "%ld-%ld%s", range
->start
,
354 range
->end
+ 1, range
->inheritable
355 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR
);
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. */
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
,
383 svn_merge_range_t
*pushed_mrange
= mrange
;
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
;
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'"),
424 pushed_mrange
= svn_merge_range_dup(mrange
, pool
);
425 APR_ARRAY_PUSH(revlist
, svn_merge_range_t
*) = pushed_mrange
;
426 *lastrange
= pushed_mrange
;
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.
440 parse_revlist(const char **input
, const char *end
,
441 apr_array_header_t
*revlist
, const char *pathname
,
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
))
451 if (*curr
== '\n' || curr
== end
)
453 /* Empty range list. */
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
!= '*'
469 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
470 _("Invalid character '%c' found in revision "
472 mrange
->start
= firstrev
- 1;
473 mrange
->end
= firstrev
;
474 mrange
->inheritable
= TRUE
;
478 svn_revnum_t secondrev
;
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 "
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
,
502 else if (*curr
== ',')
504 SVN_ERR(combine_with_adjacent_lastrange(&lastrange
, mrange
, FALSE
,
508 else if (*curr
== '*')
510 mrange
->inheritable
= FALSE
;
512 if (*curr
== ',' || *curr
== '\n' || curr
== end
)
514 SVN_ERR(combine_with_adjacent_lastrange(&lastrange
, mrange
,
515 FALSE
, revlist
, pool
));
528 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
529 _("Invalid character '%c' found in "
530 "range list"), *curr
);
535 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
536 _("Invalid character '%c' found in "
537 "range list"), *curr
);
542 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
543 _("Range list parsing ended before hitting "
549 /* revisionline -> PATHNAME COLON revisionlist */
551 parse_revision_line(const char **input
, const char *end
, svn_mergeinfo_t hash
,
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 ':'"));
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 "
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
);
583 /* top -> revisionline (NEWLINE revisionline)* */
585 parse_top(const char **input
, const char *end
, svn_mergeinfo_t hash
,
589 SVN_ERR(parse_revision_line(input
, end
, hash
, pool
));
595 svn_mergeinfo_parse(svn_mergeinfo_t
*mergeinfo
,
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'"),
614 svn_rangelist_merge(apr_array_header_t
**rangelist
,
615 apr_array_header_t
*changes
,
619 svn_merge_range_t
*lastrange
= NULL
;
620 apr_array_header_t
*output
= apr_array_make(pool
, 1,
621 sizeof(svn_merge_range_t
*));
624 while (i
< (*rangelist
)->nelts
&& j
< changes
->nelts
)
626 svn_merge_range_t
*elt1
, *elt2
;
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
);
635 /* Only when merging two non-inheritable ranges is the result also
636 non-inheritable. In all other cases ensure an inheritiable
638 if (elt1
->inheritable
|| elt2
->inheritable
)
639 elt1
->inheritable
= TRUE
;
640 combine_with_lastrange(&lastrange
, elt1
, TRUE
, output
,
647 combine_with_lastrange(&lastrange
, elt1
, TRUE
, output
,
653 combine_with_lastrange(&lastrange
, elt2
, TRUE
, output
,
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
,
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
,
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
)));
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. */
704 range_swap_endpoints(svn_merge_range_t
*range
)
706 svn_revnum_t swap
= range
->start
;
707 range
->start
= range
->end
;
712 svn_rangelist_reverse(apr_array_header_t
*rangelist
, apr_pool_t
*pool
)
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
*));
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. */
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
,
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
*));
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. */
771 wboardelt
= *(APR_ARRAY_IDX(whiteboard
, i
, svn_merge_range_t
*));
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
))
788 combine_with_lastrange(&lastrange
, elt1
, TRUE
, *output
,
789 consider_inheritance
, pool
);
793 if (elt1
->start
== elt2
->start
&& elt1
->end
== elt2
->end
)
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
;
805 /* Retain the range that falls before the eraser start. */
806 tmp_range
.start
= elt1
->start
;
807 tmp_range
.end
= elt2
->start
;
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
823 if (elt1
->end
> elt2
->end
)
825 /* The whiteboard range ends after the eraser range. */
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
;
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
851 if (svn_sort_compare_ranges(&elt2
, &elt1
) < 0)
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
;
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
);
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
);
898 svn_rangelist_intersect(apr_array_header_t
**output
,
899 apr_array_header_t
*rangelist1
,
900 apr_array_header_t
*rangelist2
,
903 return rangelist_intersect_or_remove(output
, rangelist1
, rangelist2
, FALSE
,
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
,
914 return rangelist_intersect_or_remove(output
, eraser
, whiteboard
, TRUE
,
915 consider_inheritance
, pool
);
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
,
924 /* The following diagrams illustrate some common range delta scenarios:
927 r0 <===========(=========)============[=========]===========> rHEAD
930 (from) deleted deleted
931 r0 <===========(=========[============]=========)===========> rHEAD
935 r0 <===========(=========[============)=========]===========> rHEAD
939 r0 <===========[=========(============]=========)===========> rHEAD
943 r0 <===========[=========(============)=========]===========> rHEAD
947 r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
951 /* The items that are present in from, but not in to, must have been
953 SVN_ERR(svn_rangelist_remove(deleted
, to
, from
, consider_inheritance
,
955 /* The items that are present in to, but not in from, must have been
957 SVN_ERR(svn_rangelist_remove(added
, from
, to
, consider_inheritance
, pool
));
961 struct mergeinfo_diff_baton
963 svn_mergeinfo_t from
;
965 svn_mergeinfo_t deleted
;
966 svn_mergeinfo_t added
;
967 svn_boolean_t consider_inheritance
;
971 /* This implements the 'svn_hash_diff_func_t' interface.
972 BATON is of type 'struct mergeinfo_diff_baton *'.
975 mergeinfo_hash_diff_cb(const void *key
, apr_ssize_t klen
,
976 enum svn_hash_diff_key_status status
,
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
,
1025 struct mergeinfo_diff_baton mdb
;
1028 mdb
.deleted
= deleted
;
1030 mdb
.consider_inheritance
= consider_inheritance
;
1033 SVN_ERR(svn_hash_diff(from
, to
, mergeinfo_hash_diff_cb
, &mdb
, pool
));
1035 return SVN_NO_ERROR
;
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
,
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
);
1056 *deleted
= apr_hash_make(pool
);
1057 *added
= apr_hash_make(pool
);
1061 SVN_ERR(walk_mergeinfo_hash_for_diff(from
, to
, *deleted
, *added
,
1062 consider_inheritance
, pool
));
1066 return SVN_NO_ERROR
;
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
,
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;
1087 return SVN_NO_ERROR
;
1091 svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo
, svn_mergeinfo_t changes
,
1094 apr_array_header_t
*sorted1
, *sorted2
;
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
);
1102 while (i
< sorted1
->nelts
&& j
< sorted2
->nelts
)
1104 svn_sort__item_t elt1
, elt2
;
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
);
1113 apr_array_header_t
*rl1
, *rl2
;
1118 SVN_ERR(svn_rangelist_merge(&rl1
, rl2
,
1120 apr_hash_set(mergeinfo
, elt1
.key
, elt1
.klen
, rl1
);
1130 apr_hash_set(mergeinfo
, elt2
.key
, elt2
.klen
, elt2
.value
);
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
;
1146 svn_mergeinfo_intersect(svn_mergeinfo_t
*mergeinfo
,
1147 svn_mergeinfo_t mergeinfo1
,
1148 svn_mergeinfo_t mergeinfo2
,
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
;
1166 apr_hash_this(hi
, &path
, NULL
, &val
);
1168 rangelist
= apr_hash_get(mergeinfo2
, path
, APR_HASH_KEY_STRING
);
1171 SVN_ERR(svn_rangelist_intersect(&rangelist
,
1172 (apr_array_header_t
*) val
,
1174 if (rangelist
->nelts
> 0)
1175 apr_hash_set(*mergeinfo
, path
, APR_HASH_KEY_STRING
, rangelist
);
1178 return SVN_NO_ERROR
;
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
,
1188 return SVN_NO_ERROR
;
1192 svn_rangelist_to_string(svn_string_t
**output
,
1193 const apr_array_header_t
*rangelist
,
1196 svn_stringbuf_t
*buf
= svn_stringbuf_create("", pool
);
1198 if (rangelist
->nelts
> 0)
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
,
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
);
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",
1249 if (i
< sorted
->nelts
- 1)
1250 svn_stringbuf_appendcstr(*output
, "\n");
1254 return SVN_NO_ERROR
;
1258 svn_mergeinfo_to_string(svn_string_t
**output
, svn_mergeinfo_t input
,
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
);
1269 *output
= svn_string_create("", pool
);
1271 return SVN_NO_ERROR
;
1275 svn_mergeinfo_sort(svn_mergeinfo_t input
, apr_pool_t
*pool
)
1277 apr_hash_index_t
*hi
;
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
);
1286 qsort(rl
->elts
, rl
->nelts
, rl
->elt_size
, svn_sort_compare_ranges
);
1288 return SVN_NO_ERROR
;
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
;
1297 apr_ssize_t pathlen
;
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
;
1311 svn_mergeinfo_inheritable(svn_mergeinfo_t
*output
,
1312 svn_mergeinfo_t mergeinfo
,
1318 apr_hash_index_t
*hi
;
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
,
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
;
1344 svn_rangelist_inheritable(apr_array_header_t
**inheritable_rangelist
,
1345 apr_array_header_t
*rangelist
,
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
)
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
;
1378 /* We want only the non-inheritable ranges bound by START
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
;
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
,
1397 return SVN_NO_ERROR
;
1401 svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo
,
1404 apr_hash_index_t
*hi
;
1405 svn_boolean_t removed_some_ranges
= FALSE
;
1409 for (hi
= apr_hash_first(pool
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
1414 apr_array_header_t
*rangelist
;
1416 apr_hash_this(hi
, &key
, NULL
, &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
;
1431 svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t
*out_catalog
,
1432 svn_mergeinfo_catalog_t in_catalog
,
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
))
1444 const char *original_path
;
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
*));
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
*),
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
));
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
;
1495 return rev
> range
->end
&& rev
<= range
->start
;