use gix_hash::ObjectId;
use gix_object::bstr::BString;
use smallvec::SmallVec;
use std::ops::RangeInclusive;
use std::{
num::NonZeroU32,
ops::{AddAssign, Range, SubAssign},
};
use crate::file::function::tokens_for_diffing;
use crate::Error;
#[derive(Debug, Clone, Default)]
pub struct BlameRanges {
ranges: Vec<RangeInclusive<u32>>,
}
impl BlameRanges {
pub fn new() -> Self {
Self::default()
}
pub fn from_range(range: RangeInclusive<u32>) -> Self {
Self { ranges: vec![range] }
}
pub fn from_ranges(ranges: Vec<RangeInclusive<u32>>) -> Self {
let mut result = Self::new();
for range in ranges {
result.merge_range(range);
}
result
}
}
impl BlameRanges {
pub fn add_range(&mut self, new_range: RangeInclusive<u32>) {
self.merge_range(new_range);
}
fn merge_range(&mut self, new_range: RangeInclusive<u32>) {
for range in &mut self.ranges {
if new_range.start() <= range.end() && range.start() <= new_range.end() {
*range = *range.start().min(new_range.start())..=*range.end().max(new_range.end());
return;
}
}
self.ranges.push(new_range);
}
pub fn to_zero_based_exclusive(&self, max_lines: u32) -> Result<Vec<Range<u32>>, Error> {
if self.ranges.is_empty() {
let range = 0..max_lines;
return Ok(vec![range]);
}
let mut result = Vec::with_capacity(self.ranges.len());
for range in &self.ranges {
if *range.start() == 0 {
return Err(Error::InvalidLineRange);
}
let start = range.start() - 1;
let end = *range.end();
if start >= max_lines || end > max_lines || start == end {
return Err(Error::InvalidLineRange);
}
result.push(start..end);
}
Ok(result)
}
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
}
#[derive(Default, Debug, Clone)]
pub struct Options {
pub diff_algorithm: gix_diff::blob::Algorithm,
pub range: BlameRanges,
pub since: Option<gix_date::Time>,
pub rewrites: Option<gix_diff::Rewrites>,
pub debug_track_path: bool,
}
#[derive(Clone, Debug)]
pub struct BlamePathEntry {
pub source_file_path: BString,
pub previous_source_file_path: Option<BString>,
pub commit_id: ObjectId,
pub blob_id: ObjectId,
pub previous_blob_id: ObjectId,
pub parent_index: usize,
}
#[derive(Debug, Default, Clone)]
pub struct Outcome {
pub entries: Vec<BlameEntry>,
pub blob: Vec<u8>,
pub statistics: Statistics,
pub blame_path: Option<Vec<BlamePathEntry>>,
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Statistics {
pub commits_traversed: usize,
pub trees_decoded: usize,
pub trees_diffed: usize,
pub trees_diffed_with_rewrites: usize,
pub blobs_diffed: usize,
}
impl Outcome {
pub fn entries_with_lines(&self) -> impl Iterator<Item = (BlameEntry, Vec<BString>)> + '_ {
use gix_diff::blob::intern::TokenSource;
let mut interner = gix_diff::blob::intern::Interner::new(self.blob.len() / 100);
let lines_as_tokens: Vec<_> = tokens_for_diffing(&self.blob)
.tokenize()
.map(|token| interner.intern(token))
.collect();
self.entries.iter().map(move |e| {
(
e.clone(),
lines_as_tokens[e.range_in_blamed_file()]
.iter()
.map(|token| BString::new(interner[*token].into()))
.collect(),
)
})
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Offset {
Added(u32),
Deleted(u32),
}
impl Offset {
pub fn shifted_range(&self, range: &Range<u32>) -> Range<u32> {
match self {
Offset::Added(added) => {
debug_assert!(range.start >= *added, "{self:?} {range:?}");
Range {
start: range.start - added,
end: range.end - added,
}
}
Offset::Deleted(deleted) => Range {
start: range.start + deleted,
end: range.end + deleted,
},
}
}
}
impl AddAssign<u32> for Offset {
fn add_assign(&mut self, rhs: u32) {
match self {
Self::Added(added) => *self = Self::Added(*added + rhs),
Self::Deleted(deleted) => {
if rhs > *deleted {
*self = Self::Added(rhs - *deleted);
} else {
*self = Self::Deleted(*deleted - rhs);
}
}
}
}
}
impl SubAssign<u32> for Offset {
fn sub_assign(&mut self, rhs: u32) {
match self {
Self::Added(added) => {
if rhs > *added {
*self = Self::Deleted(rhs - *added);
} else {
*self = Self::Added(*added - rhs);
}
}
Self::Deleted(deleted) => *self = Self::Deleted(*deleted + rhs),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BlameEntry {
pub start_in_blamed_file: u32,
pub start_in_source_file: u32,
pub len: NonZeroU32,
pub commit_id: ObjectId,
pub source_file_name: Option<BString>,
}
impl BlameEntry {
pub fn new(
range_in_blamed_file: Range<u32>,
range_in_source_file: Range<u32>,
commit_id: ObjectId,
source_file_name: Option<BString>,
) -> Self {
debug_assert!(
range_in_blamed_file.end > range_in_blamed_file.start,
"{range_in_blamed_file:?}"
);
debug_assert!(
range_in_source_file.end > range_in_source_file.start,
"{range_in_source_file:?}"
);
debug_assert_eq!(range_in_source_file.len(), range_in_blamed_file.len());
Self {
start_in_blamed_file: range_in_blamed_file.start,
start_in_source_file: range_in_source_file.start,
len: NonZeroU32::new(range_in_blamed_file.len() as u32).expect("BUG: hunks are never empty"),
commit_id,
source_file_name,
}
}
}
impl BlameEntry {
pub fn range_in_blamed_file(&self) -> Range<usize> {
let start = self.start_in_blamed_file as usize;
start..start + self.len.get() as usize
}
pub fn range_in_source_file(&self) -> Range<usize> {
let start = self.start_in_source_file as usize;
start..start + self.len.get() as usize
}
}
pub(crate) trait LineRange {
fn shift_by(&self, offset: Offset) -> Self;
}
impl LineRange for Range<u32> {
fn shift_by(&self, offset: Offset) -> Self {
offset.shifted_range(self)
}
}
#[derive(Debug, PartialEq)]
pub struct UnblamedHunk {
pub range_in_blamed_file: Range<u32>,
pub suspects: SmallVec<[(ObjectId, Range<u32>); 1]>,
pub source_file_name: Option<BString>,
}
impl UnblamedHunk {
pub(crate) fn has_suspect(&self, suspect: &ObjectId) -> bool {
self.suspects.iter().any(|entry| entry.0 == *suspect)
}
pub(crate) fn get_range(&self, suspect: &ObjectId) -> Option<&Range<u32>> {
self.suspects
.iter()
.find(|entry| entry.0 == *suspect)
.map(|entry| &entry.1)
}
}
#[derive(Debug)]
pub(crate) enum Either<T, U> {
Left(T),
Right(U),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Change {
Unchanged(Range<u32>),
AddedOrReplaced(Range<u32>, u32),
Deleted(u32, u32),
}