1+ use gix_hash:: ObjectId ;
2+ use gix_object:: bstr:: BString ;
3+ use smallvec:: SmallVec ;
4+ use std:: ops:: RangeInclusive ;
15use std:: {
26 num:: NonZeroU32 ,
37 ops:: { AddAssign , Range , SubAssign } ,
48} ;
59
6- use gix_hash:: ObjectId ;
7- use gix_object:: bstr:: BString ;
8- use smallvec:: SmallVec ;
9-
1010use crate :: file:: function:: tokens_for_diffing;
1111use crate :: Error ;
1212
1313/// A type to represent one or more line ranges to blame in a file.
1414///
15- /// This type handles the conversion between git's 1-based inclusive ranges and the internal
15+ /// It handles the conversion between git's 1-based inclusive ranges and the internal
1616/// 0-based exclusive ranges used by the blame algorithm.
1717///
1818/// # Examples
@@ -21,18 +21,18 @@ use crate::Error;
2121/// use gix_blame::BlameRanges;
2222///
2323/// // Blame lines 20 through 40 (inclusive)
24- /// let range = BlameRanges::from_range(20..41 );
24+ /// let range = BlameRanges::from_range(20..=40 );
2525///
2626/// // Blame multiple ranges
2727/// let mut ranges = BlameRanges::new();
28- /// ranges.add_range(1..5 ); // Lines 1-4
29- /// ranges.add_range(10..15 ); // Lines 10-14
28+ /// ranges.add_range(1..=4 ); // Lines 1-4
29+ /// ranges.add_range(10..=14 ); // Lines 10-14
3030/// ```
3131///
3232/// # Line Number Representation
3333///
3434/// This type uses 1-based inclusive ranges to mirror `git`'s behaviour:
35- /// - A range of `20..41 ` represents 21 lines, spanning from line 20 up to and including line 40
35+ /// - A range of `20..=40 ` represents 21 lines, spanning from line 20 up to and including line 40
3636/// - This will be converted to `19..40` internally as the algorithm uses 0-based ranges that are exclusive at the end
3737///
3838/// # Empty Ranges
@@ -43,59 +43,60 @@ use crate::Error;
4343pub struct BlameRanges {
4444 /// The ranges to blame, stored as 1-based inclusive ranges
4545 /// An empty Vec means blame the entire file
46- ranges : Vec < Range < u32 > > ,
46+ ranges : Vec < RangeInclusive < u32 > > ,
4747}
4848
49+ /// Lifecycle
4950impl BlameRanges {
5051 /// Create a new empty BlameRanges instance.
5152 ///
5253 /// An empty instance means to blame the entire file.
5354 pub fn new ( ) -> Self {
54- Self { ranges : Vec :: new ( ) }
55- }
56-
57- /// Add a single range to blame.
58- ///
59- /// The range should be 1-based inclusive.
60- /// If the new range overlaps with or is adjacent to an existing range,
61- /// they will be merged into a single range.
62- pub fn add_range ( & mut self , new_range : Range < u32 > ) {
63- self . merge_range ( new_range) ;
55+ Self :: default ( )
6456 }
6557
6658 /// Create from a single range.
6759 ///
68- /// The range should be 1-based inclusive , similar to git's line number format.
69- pub fn from_range ( range : Range < u32 > ) -> Self {
60+ /// The range is 1-based, similar to git's line number format.
61+ pub fn from_range ( range : RangeInclusive < u32 > ) -> Self {
7062 Self { ranges : vec ! [ range] }
7163 }
7264
7365 /// Create from multiple ranges.
7466 ///
75- /// All ranges should be 1-based inclusive .
67+ /// All ranges are 1-based.
7668 /// Overlapping or adjacent ranges will be merged.
77- pub fn from_ranges ( ranges : Vec < Range < u32 > > ) -> Self {
69+ pub fn from_ranges ( ranges : Vec < RangeInclusive < u32 > > ) -> Self {
7870 let mut result = Self :: new ( ) ;
7971 for range in ranges {
8072 result. merge_range ( range) ;
8173 }
8274 result
8375 }
76+ }
77+
78+ impl BlameRanges {
79+ /// Add a single range to blame.
80+ ///
81+ /// The range should be 1-based inclusive.
82+ /// If the new range overlaps with or is adjacent to an existing range,
83+ /// they will be merged into a single range.
84+ pub fn add_range ( & mut self , new_range : RangeInclusive < u32 > ) {
85+ self . merge_range ( new_range) ;
86+ }
8487
8588 /// Attempts to merge the new range with any existing ranges.
86- /// If no merge is possible, adds it as a new range.
87- fn merge_range ( & mut self , new_range : Range < u32 > ) {
88- // First check if this range can be merged with any existing range
89+ /// If no merge is possible, add it as a new range.
90+ fn merge_range ( & mut self , new_range : RangeInclusive < u32 > ) {
91+ // Check if this range can be merged with any existing range
8992 for range in & mut self . ranges {
9093 // Check if ranges overlap or are adjacent
91- if new_range. start <= range. end && range. start <= new_range. end {
92- // Merge the ranges by taking the minimum start and maximum end
93- range. start = range. start . min ( new_range. start ) ;
94- range. end = range. end . max ( new_range. end ) ;
94+ if new_range. start ( ) <= range. end ( ) && range. start ( ) <= new_range. end ( ) {
95+ * range = * range. start ( ) . min ( new_range. start ( ) ) ..=* range. end ( ) . max ( new_range. end ( ) ) ;
9596 return ;
9697 }
9798 }
98- // If no overlap found, add as new range
99+ // If no overlap found, add it as a new range
99100 self . ranges . push ( new_range) ;
100101 }
101102
@@ -118,11 +119,11 @@ impl BlameRanges {
118119
119120 let mut result = Vec :: with_capacity ( self . ranges . len ( ) ) ;
120121 for range in & self . ranges {
121- if range. start == 0 {
122+ if * range. start ( ) == 0 {
122123 return Err ( Error :: InvalidLineRange ) ;
123124 }
124- let start = range. start - 1 ;
125- let end = range. end ;
125+ let start = range. start ( ) - 1 ;
126+ let end = * range. end ( ) ;
126127 if start >= max_lines || end > max_lines || start == end {
127128 return Err ( Error :: InvalidLineRange ) ;
128129 }
0 commit comments