@@ -24,9 +24,11 @@ use crate::Error;
24
24
/// let range = BlameRanges::from_range(20..=40);
25
25
///
26
26
/// // 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
+ /// );
30
32
/// ```
31
33
///
32
34
/// # Line Number Representation
@@ -40,36 +42,33 @@ use crate::Error;
40
42
/// An empty `BlameRanges` (created via `BlameRanges::new()` or `BlameRanges::default()`) means
41
43
/// to blame the entire file, similar to running `git blame` without line number arguments.
42
44
#[ 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 > > ) ,
47
53
}
48
54
49
55
/// Lifecycle
50
56
impl 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
-
58
57
/// Create from a single range.
59
58
///
60
59
/// The range is 1-based, similar to git's line number format.
61
60
pub fn from_range ( range : RangeInclusive < u32 > ) -> Self {
62
- Self { ranges : vec ! [ range] }
61
+ Self :: OneBasedInclusive ( vec ! [ range] )
63
62
}
64
63
65
64
/// Create from multiple ranges.
66
65
///
67
66
/// All ranges are 1-based.
68
67
/// Overlapping or adjacent ranges will be merged.
69
68
pub fn from_ranges ( ranges : Vec < RangeInclusive < u32 > > ) -> Self {
70
- let mut result = Self :: new ( ) ;
69
+ let mut result = Self :: OneBasedInclusive ( vec ! [ ] ) ;
71
70
for range in ranges {
72
- result. merge_range ( range) ;
71
+ let _ = result. merge_range ( range) ;
73
72
}
74
73
result
75
74
}
@@ -81,23 +80,32 @@ impl BlameRanges {
81
80
/// The range should be 1-based inclusive.
82
81
/// If the new range overlaps with or is adjacent to an existing range,
83
82
/// 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
+ }
86
88
}
87
89
88
90
/// Attempts to merge the new range with any existing ranges.
89
91
/// 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 ( ( ) )
97
106
}
107
+ _ => Err ( Error :: InvalidOneBasedLineRange ) ,
98
108
}
99
- // If no overlap found, add it as a new range
100
- self . ranges . push ( new_range) ;
101
109
}
102
110
103
111
/// Convert the 1-based inclusive ranges to 0-based exclusive ranges.
@@ -111,30 +119,26 @@ impl BlameRanges {
111
119
/// - Any range starts at 0 (must be 1-based)
112
120
/// - Any range extends beyond the file's length
113
121
/// - 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] ) )
124
127
}
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) )
129
139
}
130
- result . push ( start..end ) ;
140
+ Self :: ZeroBasedExclusive ( _ ) => Ok ( self . clone ( ) ) ,
131
141
}
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 ( )
138
142
}
139
143
}
140
144
@@ -334,6 +338,17 @@ pub struct UnblamedHunk {
334
338
}
335
339
336
340
impl 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
+
337
352
pub ( crate ) fn has_suspect ( & self , suspect : & ObjectId ) -> bool {
338
353
self . suspects . iter ( ) . any ( |entry| entry. 0 == * suspect)
339
354
}
0 commit comments