1use std::{
2 num::NonZeroU32,
3 ops::{AddAssign, Range, SubAssign},
4};
5
6use gix_hash::ObjectId;
7use gix_object::bstr::BString;
8use smallvec::SmallVec;
9
10use crate::file::function::tokens_for_diffing;
11
12#[derive(Default, Debug, Clone)]
14pub struct Options {
15 pub diff_algorithm: gix_diff::blob::Algorithm,
17 pub range: Option<std::ops::Range<u32>>,
21 pub since: Option<gix_date::Time>,
23}
24
25#[derive(Debug, Default, Clone)]
27pub struct Outcome {
28 pub entries: Vec<BlameEntry>,
31 pub blob: Vec<u8>,
33 pub statistics: Statistics,
35}
36
37#[derive(Debug, Default, Copy, Clone)]
39pub struct Statistics {
40 pub commits_traversed: usize,
42 pub trees_decoded: usize,
44 pub trees_diffed: usize,
48 pub blobs_diffed: usize,
51}
52
53impl Outcome {
54 pub fn entries_with_lines(&self) -> impl Iterator<Item = (BlameEntry, Vec<BString>)> + '_ {
59 use gix_diff::blob::intern::TokenSource;
60 let mut interner = gix_diff::blob::intern::Interner::new(self.blob.len() / 100);
61 let lines_as_tokens: Vec<_> = tokens_for_diffing(&self.blob)
62 .tokenize()
63 .map(|token| interner.intern(token))
64 .collect();
65 self.entries.iter().map(move |e| {
66 (
67 e.clone(),
68 lines_as_tokens[e.range_in_blamed_file()]
69 .iter()
70 .map(|token| BString::new(interner[*token].into()))
71 .collect(),
72 )
73 })
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq)]
79pub enum Offset {
80 Added(u32),
82 Deleted(u32),
84}
85
86impl Offset {
87 pub fn shifted_range(&self, range: &Range<u32>) -> Range<u32> {
89 match self {
90 Offset::Added(added) => {
91 debug_assert!(range.start >= *added, "{self:?} {range:?}");
92 Range {
93 start: range.start - added,
94 end: range.end - added,
95 }
96 }
97 Offset::Deleted(deleted) => Range {
98 start: range.start + deleted,
99 end: range.end + deleted,
100 },
101 }
102 }
103}
104
105impl AddAssign<u32> for Offset {
106 fn add_assign(&mut self, rhs: u32) {
107 match self {
108 Self::Added(added) => *self = Self::Added(*added + rhs),
109 Self::Deleted(deleted) => {
110 if rhs > *deleted {
111 *self = Self::Added(rhs - *deleted);
112 } else {
113 *self = Self::Deleted(*deleted - rhs);
114 }
115 }
116 }
117 }
118}
119
120impl SubAssign<u32> for Offset {
121 fn sub_assign(&mut self, rhs: u32) {
122 match self {
123 Self::Added(added) => {
124 if rhs > *added {
125 *self = Self::Deleted(rhs - *added);
126 } else {
127 *self = Self::Added(*added - rhs);
128 }
129 }
130 Self::Deleted(deleted) => *self = Self::Deleted(*deleted + rhs),
131 }
132 }
133}
134
135#[derive(Clone, Debug, PartialEq)]
140pub struct BlameEntry {
141 pub start_in_blamed_file: u32,
143 pub start_in_source_file: u32,
147 pub len: NonZeroU32,
149 pub commit_id: ObjectId,
151}
152
153impl BlameEntry {
154 pub fn new(range_in_blamed_file: Range<u32>, range_in_source_file: Range<u32>, commit_id: ObjectId) -> Self {
156 debug_assert!(
157 range_in_blamed_file.end > range_in_blamed_file.start,
158 "{range_in_blamed_file:?}"
159 );
160 debug_assert!(
161 range_in_source_file.end > range_in_source_file.start,
162 "{range_in_source_file:?}"
163 );
164 debug_assert_eq!(range_in_source_file.len(), range_in_blamed_file.len());
165
166 Self {
167 start_in_blamed_file: range_in_blamed_file.start,
168 start_in_source_file: range_in_source_file.start,
169 len: NonZeroU32::new(range_in_blamed_file.len() as u32).expect("BUG: hunks are never empty"),
170 commit_id,
171 }
172 }
173}
174
175impl BlameEntry {
176 pub fn range_in_blamed_file(&self) -> Range<usize> {
178 let start = self.start_in_blamed_file as usize;
179 start..start + self.len.get() as usize
180 }
181 pub fn range_in_source_file(&self) -> Range<usize> {
183 let start = self.start_in_source_file as usize;
184 start..start + self.len.get() as usize
185 }
186}
187
188pub(crate) trait LineRange {
189 fn shift_by(&self, offset: Offset) -> Self;
190}
191
192impl LineRange for Range<u32> {
193 fn shift_by(&self, offset: Offset) -> Self {
194 offset.shifted_range(self)
195 }
196}
197
198#[derive(Debug, PartialEq)]
200pub struct UnblamedHunk {
201 pub range_in_blamed_file: Range<u32>,
203 pub suspects: SmallVec<[(ObjectId, Range<u32>); 1]>,
207}
208
209impl UnblamedHunk {
210 pub(crate) fn has_suspect(&self, suspect: &ObjectId) -> bool {
211 self.suspects.iter().any(|entry| entry.0 == *suspect)
212 }
213
214 pub(crate) fn get_range(&self, suspect: &ObjectId) -> Option<&Range<u32>> {
215 self.suspects
216 .iter()
217 .find(|entry| entry.0 == *suspect)
218 .map(|entry| &entry.1)
219 }
220}
221
222#[derive(Debug)]
223pub(crate) enum Either<T, U> {
224 Left(T),
225 Right(U),
226}
227
228#[derive(Debug, PartialEq)]
230pub enum Change {
231 Unchanged(Range<u32>),
233 AddedOrReplaced(Range<u32>, u32),
235 Deleted(u32, u32),
237}