@@ -8,16 +8,142 @@ use gix_object::bstr::BString;
88use  smallvec:: SmallVec ; 
99
1010use  crate :: file:: function:: tokens_for_diffing; 
11+ use  crate :: Error ; 
12+ 
13+ /// A type to represent one or more line ranges to blame in a file. 
14+ /// 
15+ /// This type handles the conversion between git's 1-based inclusive ranges and the internal 
16+ /// 0-based exclusive ranges used by the blame algorithm. 
17+ /// 
18+ /// # Examples 
19+ /// 
20+ /// ```rust 
21+ /// use gix_blame::BlameRanges; 
22+ /// 
23+ /// // Blame lines 20 through 40 (inclusive) 
24+ /// let range = BlameRanges::from_range(20..41); 
25+ /// 
26+ /// // Blame multiple ranges 
27+ /// let mut ranges = BlameRanges::new(); 
28+ /// ranges.add_range(1..5);   // Lines 1-4 
29+ /// ranges.add_range(10..15); // Lines 10-14 
30+ /// ``` 
31+ /// 
32+ /// # Line Number Representation 
33+ /// 
34+ /// 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 
36+ /// - This will be converted to `19..40` internally as the algorithm uses 0-based ranges that are exclusive at the end 
37+ /// 
38+ /// # Empty Ranges 
39+ /// 
40+ /// An empty `BlameRanges` (created via `BlameRanges::new()` or `BlameRanges::default()`) means 
41+ /// to blame the entire file, similar to running `git blame` without line number arguments. 
42+ #[ 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 < Range < u32 > > , 
47+ } 
48+ 
49+ impl  BlameRanges  { 
50+     /// Create a new empty BlameRanges instance. 
51+ /// 
52+ /// An empty instance means to blame the entire file. 
53+ 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) ; 
64+     } 
65+ 
66+     /// Create from a single range. 
67+ /// 
68+ /// The range should be 1-based inclusive, similar to git's line number format. 
69+ pub  fn  from_range ( range :  Range < u32 > )  -> Self  { 
70+         Self  {  ranges :  vec ! [ range]  } 
71+     } 
72+ 
73+     /// Create from multiple ranges. 
74+ /// 
75+ /// All ranges should be 1-based inclusive. 
76+ /// Overlapping or adjacent ranges will be merged. 
77+ pub  fn  from_ranges ( ranges :  Vec < Range < u32 > > )  -> Self  { 
78+         let  mut  result = Self :: new ( ) ; 
79+         for  range in  ranges { 
80+             result. merge_range ( range) ; 
81+         } 
82+         result
83+     } 
84+ 
85+     /// 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+         for  range in  & mut  self . ranges  { 
90+             // 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 ) ; 
95+                 return ; 
96+             } 
97+         } 
98+         // If no overlap found, add as new range 
99+         self . ranges . push ( new_range) ; 
100+     } 
101+ 
102+     /// Convert the 1-based inclusive ranges to 0-based exclusive ranges. 
103+ /// 
104+ /// This is used internally by the blame algorithm to convert from git's line number format 
105+ /// to the internal format used for processing. 
106+ /// 
107+ /// # Errors 
108+ /// 
109+ /// Returns `Error::InvalidLineRange` if: 
110+ /// - Any range starts at 0 (must be 1-based) 
111+ /// - Any range extends beyond the file's length 
112+ /// - Any range has the same start and end 
113+ pub  fn  to_zero_based_exclusive ( & self ,  max_lines :  u32 )  -> Result < Vec < Range < u32 > > ,  Error >  { 
114+         if  self . ranges . is_empty ( )  { 
115+             let  range = 0 ..max_lines; 
116+             return  Ok ( vec ! [ range] ) ; 
117+         } 
118+ 
119+         let  mut  result = Vec :: with_capacity ( self . ranges . len ( ) ) ; 
120+         for  range in  & self . ranges  { 
121+             if  range. start  == 0  { 
122+                 return  Err ( Error :: InvalidLineRange ) ; 
123+             } 
124+             let  start = range. start  - 1 ; 
125+             let  end = range. end ; 
126+             if  start >= max_lines || end > max_lines || start == end { 
127+                 return  Err ( Error :: InvalidLineRange ) ; 
128+             } 
129+             result. push ( start..end) ; 
130+         } 
131+         Ok ( result) 
132+     } 
133+ 
134+     /// Returns true if no specific ranges are set (meaning blame entire file) 
135+ pub  fn  is_empty ( & self )  -> bool  { 
136+         self . ranges . is_empty ( ) 
137+     } 
138+ } 
11139
12140/// Options to be passed to [`file()`](crate::file()). 
13141#[ derive( Default ,  Debug ,  Clone ) ]  
14142pub  struct  Options  { 
15143    /// The algorithm to use for diffing. 
16144pub  diff_algorithm :  gix_diff:: blob:: Algorithm , 
17-     /// A 1-based inclusive range, in order to mirror `git`’s behaviour. `Some(20..40)` represents 
18- /// 21 lines, spanning from line 20 up to and including line 40. This will be converted to 
19- /// `19..40` internally as the algorithm uses 0-based ranges that are exclusive at the end. 
20- pub  range :  Option < std:: ops:: Range < u32 > > , 
145+     /// The ranges to blame in the file. 
146+ pub  range :  BlameRanges , 
21147    /// Don't consider commits before the given date. 
22148pub  since :  Option < gix_date:: Time > , 
23149} 
0 commit comments