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"
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
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
;
68 /* pathname -> PATHNAME */
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);
82 if ((*pathname
)->len
== 0)
83 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
84 _("No pathname preceeding ':'"));
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
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'________
108 Update end field Push Account for trimmed
109 | | range from *LASTRANGE.
111 | | maintain sort order.
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
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
,
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
;
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
;
149 || (!ranges_intersect
|| (!ranges_have_same_inheritance
150 && consider_inheritance
)))
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. */
163 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
198 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
216 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
233 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
;
245 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
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
260 if (!((*lastrange
)->end
> mrange
->end
261 && (*lastrange
)->inheritable
))
263 tmp_revnum
= (*lastrange
)->end
;
264 if (!(*lastrange
)->inheritable
)
265 (*lastrange
)->end
= mrange
->start
;
267 mrange
->start
= (*lastrange
)->end
;
269 pushed_mrange_1
= svn_merge_range_dup(mrange
, pool
);
271 pushed_mrange_1
= mrange
;
273 if (tmp_revnum
> mrange
->end
)
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
;
324 APR_ARRAY_PUSH(revlist
, svn_merge_range_t
*) = pushed_mrange_1
;
325 *lastrange
= pushed_mrange_1
;
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 *. */
336 range_to_string(svn_string_t
**result
, svn_merge_range_t
*range
,
339 if (range
->start
== range
->end
- 1)
340 *result
= svn_string_createf(pool
, "%ld%s", range
->end
,
342 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR
);
344 *result
= svn_string_createf(pool
, "%ld-%ld%s", range
->start
+ 1,
345 range
->end
, range
->inheritable
346 ? "" : SVN_MERGEINFO_NONINHERITABLE_STR
);
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. */
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
,
374 svn_merge_range_t
*pushed_mrange
= mrange
;
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
;
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'"),
415 pushed_mrange
= svn_merge_range_dup(mrange
, pool
);
416 APR_ARRAY_PUSH(revlist
, svn_merge_range_t
*) = pushed_mrange
;
417 *lastrange
= pushed_mrange
;
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.
431 parse_revlist(const char **input
, const char *end
,
432 apr_array_header_t
*revlist
, const char *pathname
,
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
))
442 if (*curr
== '\n' || curr
== end
)
444 /* Empty range list. */
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
!= '*'
460 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
461 _("Invalid character '%c' found in revision "
463 mrange
->start
= firstrev
- 1;
464 mrange
->end
= firstrev
;
465 mrange
->inheritable
= TRUE
;
469 svn_revnum_t secondrev
;
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 "
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
,
493 else if (*curr
== ',')
495 SVN_ERR(combine_with_adjacent_lastrange(&lastrange
, mrange
, FALSE
,
499 else if (*curr
== '*')
501 mrange
->inheritable
= FALSE
;
503 if (*curr
== ',' || *curr
== '\n' || curr
== end
)
505 SVN_ERR(combine_with_adjacent_lastrange(&lastrange
, mrange
,
506 FALSE
, revlist
, pool
));
519 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
520 _("Invalid character '%c' found in "
521 "range list"), *curr
);
526 return svn_error_createf(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
527 _("Invalid character '%c' found in "
528 "range list"), *curr
);
533 return svn_error_create(SVN_ERR_MERGEINFO_PARSE_ERROR
, NULL
,
534 _("Range list parsing ended before hitting "
540 /* revisionline -> PATHNAME COLON revisionlist */
542 parse_revision_line(const char **input
, const char *end
, svn_mergeinfo_t hash
,
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 ':'"));
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 "
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
);
574 /* top -> revisionline (NEWLINE revisionline)* */
576 parse_top(const char **input
, const char *end
, svn_mergeinfo_t hash
,
580 SVN_ERR(parse_revision_line(input
, end
, hash
, pool
));
585 /* Parse mergeinfo. */
587 svn_mergeinfo_parse(svn_mergeinfo_t
*mergeinfo
,
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. */
599 svn_rangelist_merge(apr_array_header_t
**rangelist
,
600 apr_array_header_t
*changes
,
604 svn_merge_range_t
*lastrange
= NULL
;
605 apr_array_header_t
*output
= apr_array_make(pool
, 1,
606 sizeof(svn_merge_range_t
*));
609 while (i
< (*rangelist
)->nelts
&& j
< changes
->nelts
)
611 svn_merge_range_t
*elt1
, *elt2
;
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
);
620 /* Only when merging two non-inheritable ranges is the result also
621 non-inheritable. In all other cases ensure an inheritiable
623 if (elt1
->inheritable
|| elt2
->inheritable
)
624 elt1
->inheritable
= TRUE
;
625 combine_with_lastrange(&lastrange
, elt1
, TRUE
, output
,
632 combine_with_lastrange(&lastrange
, elt1
, TRUE
, output
,
638 combine_with_lastrange(&lastrange
, elt2
, TRUE
, output
,
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
,
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
,
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
)));
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. */
689 range_swap_endpoints(svn_merge_range_t
*range
)
691 svn_revnum_t swap
= range
->start
;
692 range
->start
= range
->end
;
697 svn_rangelist_reverse(apr_array_header_t
*rangelist
, apr_pool_t
*pool
)
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
*));
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. */
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
,
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
*));
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. */
756 wboardelt
= *(APR_ARRAY_IDX(whiteboard
, i
, svn_merge_range_t
*));
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
))
773 combine_with_lastrange(&lastrange
, elt1
, TRUE
, *output
,
774 consider_inheritance
, pool
);
778 if (elt1
->start
== elt2
->start
&& elt1
->end
== elt2
->end
)
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
;
790 /* Retain the range that falls before the eraser start. */
791 tmp_range
.start
= elt1
->start
;
792 tmp_range
.end
= elt2
->start
;
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
808 if (elt1
->end
> elt2
->end
)
810 /* The whiteboard range ends after the eraser range. */
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
;
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
836 if (svn_sort_compare_ranges(&elt2
, &elt1
) < 0)
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
;
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
);
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
);
882 /* Expected to handle all the range overlap cases: non, partial, full */
884 svn_rangelist_intersect(apr_array_header_t
**output
,
885 apr_array_header_t
*rangelist1
,
886 apr_array_header_t
*rangelist2
,
889 return rangelist_intersect_or_remove(output
, rangelist1
, rangelist2
, FALSE
,
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
,
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:
909 r0 <===========(=========)============[=========]===========> rHEAD
912 (from) deleted deleted
913 r0 <===========(=========[============]=========)===========> rHEAD
917 r0 <===========(=========[============)=========]===========> rHEAD
921 r0 <===========[=========(============]=========)===========> rHEAD
925 r0 <===========[=========(============)=========]===========> rHEAD
929 r0 <===(=[=)=]=[==]=[=(=)=]=[=]=[=(===|===(=)==|=|==[=(=]=)=> rHEAD
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
,
938 /* The items that are present in from, but not in to, must have been
940 SVN_ERR(svn_rangelist_remove(deleted
, to
, from
, consider_inheritance
,
942 /* The items that are present in to, but not in from, must have been
944 SVN_ERR(svn_rangelist_remove(added
, from
, to
, consider_inheritance
, pool
));
949 svn_rangelist_count_revs(apr_array_header_t
*rangelist
)
951 apr_uint64_t nbr_revs
= 0;
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
;
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. */
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
,
976 apr_hash_index_t
*hi
;
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
);
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
);
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
);
1008 apr_hash_set(deleted
, apr_pstrdup(pool
, path
), APR_HASH_KEY_STRING
,
1009 svn_rangelist_dup(from_rangelist
, pool
));
1012 /* Handle path additions. */
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
);
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
;
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
,
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
);
1050 *deleted
= apr_hash_make(pool
);
1051 *added
= apr_hash_make(pool
);
1055 SVN_ERR(walk_mergeinfo_hash_for_diff(from
, to
, *deleted
, *added
,
1056 consider_inheritance
, pool
));
1060 return SVN_NO_ERROR
;
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
,
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;
1081 return SVN_NO_ERROR
;
1085 svn_mergeinfo_merge(svn_mergeinfo_t mergeinfo
, svn_mergeinfo_t changes
,
1088 apr_array_header_t
*sorted1
, *sorted2
;
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
);
1096 while (i
< sorted1
->nelts
&& j
< sorted2
->nelts
)
1098 svn_sort__item_t elt1
, elt2
;
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
);
1107 apr_array_header_t
*rl1
, *rl2
;
1112 SVN_ERR(svn_rangelist_merge(&rl1
, rl2
,
1114 apr_hash_set(mergeinfo
, elt1
.key
, elt1
.klen
, rl1
);
1124 apr_hash_set(mergeinfo
, elt2
.key
, elt2
.klen
, elt2
.value
);
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
;
1140 svn_mergeinfo_intersect(svn_mergeinfo_t
*mergeinfo
,
1141 svn_mergeinfo_t mergeinfo1
,
1142 svn_mergeinfo_t mergeinfo2
,
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
;
1160 apr_hash_this(hi
, &path
, NULL
, &val
);
1162 rangelist
= apr_hash_get(mergeinfo2
, path
, APR_HASH_KEY_STRING
);
1165 SVN_ERR(svn_rangelist_intersect(&rangelist
,
1166 (apr_array_header_t
*) val
,
1168 if (rangelist
->nelts
> 0)
1169 apr_hash_set(*mergeinfo
, path
, APR_HASH_KEY_STRING
, rangelist
);
1172 return SVN_NO_ERROR
;
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
,
1182 return SVN_NO_ERROR
;
1186 svn_rangelist_to_string(svn_string_t
**output
,
1187 const apr_array_header_t
*rangelist
,
1190 svn_stringbuf_t
*buf
= svn_stringbuf_create("", pool
);
1192 if (rangelist
->nelts
> 0)
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
,
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
);
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",
1243 if (i
< sorted
->nelts
- 1)
1244 svn_stringbuf_appendcstr(*output
, "\n");
1248 return SVN_NO_ERROR
;
1252 svn_mergeinfo_to_string(svn_string_t
**output
, svn_mergeinfo_t input
,
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
);
1263 *output
= svn_string_create("", pool
);
1265 return SVN_NO_ERROR
;
1268 /* Perform an in-place sort of the rangelists in a mergeinfo hash. */
1270 svn_mergeinfo_sort(svn_mergeinfo_t input
, apr_pool_t
*pool
)
1272 apr_hash_index_t
*hi
;
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
);
1281 qsort(rl
->elts
, rl
->nelts
, rl
->elt_size
, svn_sort_compare_ranges
);
1283 return SVN_NO_ERROR
;
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
;
1292 apr_ssize_t pathlen
;
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
;
1306 svn_mergeinfo_inheritable(svn_mergeinfo_t
*output
,
1307 svn_mergeinfo_t mergeinfo
,
1313 apr_hash_index_t
*hi
;
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
,
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
;
1339 svn_rangelist_inheritable(apr_array_header_t
**inheritable_rangelist
,
1340 apr_array_header_t
*rangelist
,
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
)
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
;
1373 /* We want only the non-inheritable ranges bound by START
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
;
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
,
1392 return SVN_NO_ERROR
;
1396 svn_mergeinfo__remove_empty_rangelists(svn_mergeinfo_t mergeinfo
,
1399 apr_hash_index_t
*hi
;
1400 svn_boolean_t removed_some_ranges
= FALSE
;
1404 for (hi
= apr_hash_first(pool
, mergeinfo
); hi
; hi
= apr_hash_next(hi
))
1409 apr_array_header_t
*rangelist
;
1411 apr_hash_this(hi
, &key
, NULL
, &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
;
1426 svn_mergeinfo__remove_prefix_from_catalog(svn_mergeinfo_catalog_t
*out_catalog
,
1427 svn_mergeinfo_catalog_t in_catalog
,
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
))
1439 const char *original_path
;
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
*));
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
*),
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
));
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
;
1490 return rev
> range
->end
&& rev
<= range
->start
;