1
+
use gix_hash::ObjectId;
2
+
use gix_object::bstr::BString;
3
+
use smallvec::SmallVec;
4
+
use std::ops::RangeInclusive;
1
5
use std::{
2
6
num::NonZeroU32,
3
7
ops::{AddAssign, Range, SubAssign},
4
8
};
5
9
6
-
use gix_hash::ObjectId;
7
-
use gix_object::bstr::BString;
8
-
use smallvec::SmallVec;
9
-
10
10
use 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
+
/// It 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..=40);
25
+
///
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
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..=40` 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<RangeInclusive<u32>>,
47
+
}
48
+
49
+
/// Lifecycle
50
+
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
+
/// Create from a single range.
59
+
///
60
+
/// The range is 1-based, similar to git's line number format.
61
+
pub fn from_range(range: RangeInclusive<u32>) -> Self {
62
+
Self { ranges: vec![range] }
63
+
}
64
+
65
+
/// Create from multiple ranges.
66
+
///
67
+
/// All ranges are 1-based.
68
+
/// Overlapping or adjacent ranges will be merged.
69
+
pub fn from_ranges(ranges: Vec<RangeInclusive<u32>>) -> Self {
70
+
let mut result = Self::new();
71
+
for range in ranges {
72
+
result.merge_range(range);
73
+
}
74
+
result
75
+
}
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
+
}
87
+
88
+
/// Attempts to merge the new range with any existing ranges.
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
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;
97
+
}
98
+
}
99
+
// If no overlap found, add it as a new range
100
+
self.ranges.push(new_range);
101
+
}
102
+
103
+
/// Convert the 1-based inclusive ranges to 0-based exclusive ranges.
104
+
///
105
+
/// This is used internally by the blame algorithm to convert from git's line number format
106
+
/// to the internal format used for processing.
107
+
///
108
+
/// # Errors
109
+
///
110
+
/// Returns `Error::InvalidLineRange` if:
111
+
/// - Any range starts at 0 (must be 1-based)
112
+
/// - Any range extends beyond the file's length
113
+
/// - 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);
124
+
}
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);
129
+
}
130
+
result.push(start..end);
131
+
}
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
+
}
139
+
}
11
140
12
141
/// Options to be passed to [`file()`](crate::file()).
13
142
#[derive(Default, Debug, Clone)]
14
143
pub struct Options {
15
144
/// The algorithm to use for diffing.
16
145
pub 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>>,
146
+
/// The ranges to blame in the file.
147
+
pub range: BlameRanges,
21
148
/// Don't consider commits before the given date.
22
149
pub since: Option<gix_date::Time>,
23
150
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4