@@ -24,9 +24,11 @@ use crate::Error;
2424/// let range = BlameRanges::from_range(20..=40);
2525///
2626/// // Blame multiple ranges
27- /// let mut ranges = BlameRanges::new();
28- /// ranges.add_range(1..=4); // Lines 1-4
29- /// ranges.add_range(10..=14); // Lines 10-14
27+ /// let mut ranges = BlameRanges::from_ranges(vec![
28+ /// 1..=4, // Lines 1-4
29+ /// 10..=14, // Lines 10-14
30+ /// ]
31+ /// );
3032/// ```
3133///
3234/// # Line Number Representation
@@ -40,36 +42,33 @@ use crate::Error;
4042/// An empty `BlameRanges` (created via `BlameRanges::new()` or `BlameRanges::default()`) means
4143/// to blame the entire file, similar to running `git blame` without line number arguments.
4244#[ derive( Debug , Clone , Default ) ]
43- pub struct BlameRanges {
44- /// The ranges to blame, stored as 1-based inclusive ranges
45- /// An empty Vec means blame the entire file
46- ranges : Vec < RangeInclusive < u32 > > ,
45+ pub enum BlameRanges {
46+ /// Blame the entire file.
47+ #[ default]
48+ FullFile ,
49+ /// Blame ranges in 1-based inclusive format.
50+ OneBasedInclusive ( Vec < RangeInclusive < u32 > > ) ,
51+ /// Blame ranges in 0-based exclusive format.
52+ ZeroBasedExclusive ( Vec < Range < u32 > > ) ,
4753}
4854
4955/// Lifecycle
5056impl BlameRanges {
51- /// Create a new empty BlameRanges instance.
52- ///
53- /// An empty instance means to blame the entire file.
54- pub fn new ( ) -> Self {
55- Self :: default ( )
56- }
57-
5857 /// Create from a single range.
5958 ///
6059 /// The range is 1-based, similar to git's line number format.
6160 pub fn from_range ( range : RangeInclusive < u32 > ) -> Self {
62- Self { ranges : vec ! [ range] }
61+ Self :: OneBasedInclusive ( vec ! [ range] )
6362 }
6463
6564 /// Create from multiple ranges.
6665 ///
6766 /// All ranges are 1-based.
6867 /// Overlapping or adjacent ranges will be merged.
6968 pub fn from_ranges ( ranges : Vec < RangeInclusive < u32 > > ) -> Self {
70- let mut result = Self :: new ( ) ;
69+ let mut result = Self :: OneBasedInclusive ( vec ! [ ] ) ;
7170 for range in ranges {
72- result. merge_range ( range) ;
71+ let _ = result. merge_range ( range) ;
7372 }
7473 result
7574 }
@@ -81,23 +80,32 @@ impl BlameRanges {
8180 /// The range should be 1-based inclusive.
8281 /// If the new range overlaps with or is adjacent to an existing range,
8382 /// 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) ;
83+ pub fn add_range ( & mut self , new_range : RangeInclusive < u32 > ) -> Result < ( ) , Error > {
84+ match self {
85+ Self :: OneBasedInclusive ( _) => self . merge_range ( new_range) ,
86+ _ => Err ( Error :: InvalidOneBasedLineRange ) ,
87+ }
8688 }
8789
8890 /// Attempts to merge the new range with any existing ranges.
8991 /// 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
92- for range in & mut self . ranges {
93- // Check if ranges overlap or are adjacent
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 ( ) ) ;
96- return ;
92+ fn merge_range ( & mut self , new_range : RangeInclusive < u32 > ) -> Result < ( ) , Error > {
93+ match self {
94+ Self :: OneBasedInclusive ( ref mut ranges) => {
95+ // Check if this range can be merged with any existing range
96+ for range in & mut * ranges {
97+ // Check if ranges overlap or are adjacent
98+ if new_range. start ( ) <= & ( range. end ( ) + 1 ) && range. start ( ) <= & ( new_range. end ( ) + 1 ) {
99+ * range = * range. start ( ) . min ( new_range. start ( ) ) ..=* range. end ( ) . max ( new_range. end ( ) ) ;
100+ return Ok ( ( ) ) ;
101+ }
102+ }
103+ // If no overlap found, add it as a new range
104+ ranges. push ( new_range) ;
105+ Ok ( ( ) )
97106 }
107+ _ => Err ( Error :: InvalidOneBasedLineRange ) ,
98108 }
99- // If no overlap found, add it as a new range
100- self . ranges . push ( new_range) ;
101109 }
102110
103111 /// Convert the 1-based inclusive ranges to 0-based exclusive ranges.
@@ -111,30 +119,26 @@ impl BlameRanges {
111119 /// - Any range starts at 0 (must be 1-based)
112120 /// - Any range extends beyond the file's length
113121 /// - Any range has the same start and end
114- pub fn to_zero_based_exclusive ( & self , max_lines : u32 ) -> Result < Vec < Range < u32 > > , Error > {
115- if self . ranges . is_empty ( ) {
116- let range = 0 ..max_lines;
117- return Ok ( vec ! [ range] ) ;
118- }
119-
120- let mut result = Vec :: with_capacity ( self . ranges . len ( ) ) ;
121- for range in & self . ranges {
122- if * range. start ( ) == 0 {
123- return Err ( Error :: InvalidLineRange ) ;
122+ pub fn to_zero_based_exclusive ( & self , max_lines : u32 ) -> Result < BlameRanges , Error > {
123+ match self {
124+ Self :: FullFile => {
125+ let range = 0 ..max_lines;
126+ Ok ( BlameRanges :: ZeroBasedExclusive ( vec ! [ range] ) )
124127 }
125- let start = range. start ( ) - 1 ;
126- let end = * range. end ( ) ;
127- if start >= max_lines || end > max_lines || start == end {
128- return Err ( Error :: InvalidLineRange ) ;
128+ Self :: OneBasedInclusive ( ranges) => {
129+ let mut result = Vec :: with_capacity ( ranges. len ( ) ) ;
130+ for range in ranges {
131+ let start = range. start ( ) - 1 ;
132+ let end = * range. end ( ) ;
133+ if start >= max_lines || end > max_lines || start == end {
134+ return Err ( Error :: InvalidOneBasedLineRange ) ;
135+ }
136+ result. push ( start..end) ;
137+ }
138+ Ok ( BlameRanges :: ZeroBasedExclusive ( result) )
129139 }
130- result . push ( start..end ) ;
140+ Self :: ZeroBasedExclusive ( _ ) => Ok ( self . clone ( ) ) ,
131141 }
132- Ok ( result)
133- }
134-
135- /// Returns true if no specific ranges are set (meaning blame entire file)
136- pub fn is_empty ( & self ) -> bool {
137- self . ranges . is_empty ( )
138142 }
139143}
140144
@@ -334,6 +338,17 @@ pub struct UnblamedHunk {
334338}
335339
336340impl UnblamedHunk {
341+ /// Create a new instance
342+ pub fn new ( range : Range < u32 > , suspect : ObjectId ) -> Self {
343+ let range_start = range. start ;
344+ let range_end = range. end ;
345+
346+ UnblamedHunk {
347+ range_in_blamed_file : range_start..range_end,
348+ suspects : [ ( suspect, range_start..range_end) ] . into ( ) ,
349+ }
350+ }
351+
337352 pub ( crate ) fn has_suspect ( & self , suspect : & ObjectId ) -> bool {
338353 self . suspects . iter ( ) . any ( |entry| entry. 0 == * suspect)
339354 }
0 commit comments