use std::error::Error;
use git2::{DiffLine, Patch};
const PROBE_LINES: usize = 3;
#[derive(Default)]
pub(super) struct Touched {
pub new: Vec<usize>,
pub old: Vec<usize>,
pub probe: Vec<(usize, String)>,
}
struct Cursor {
new: usize,
old: usize,
}
impl Touched {
fn record(&mut self, line: &DiffLine, at: &mut Cursor) {
let origin = line.origin();
if matches!(origin, '+' | '-') {
self.new.push(at.new);
self.old.push(at.old);
}
if origin == '+' {
let text = String::from_utf8_lossy(line.content());
self.probe.push((at.new, text.trim_end().to_string()));
}
at.new += usize::from(matches!(origin, '+' | ' '));
at.old += usize::from(matches!(origin, '-' | ' '));
}
pub fn of(patch: &Patch) -> Result<Self, Box<dyn Error>> {
let mut touched = Self::default();
for hunk in 0..patch.num_hunks() {
let (header, count) = patch.hunk(hunk)?;
let mut at = Cursor {
new: (header.new_start() as usize).max(1),
old: (header.old_start() as usize).max(1),
};
for line in 0..count {
touched.record(&patch.line_in_hunk(hunk, line)?, &mut at);
}
}
for lines in [&mut touched.new, &mut touched.old] {
lines.sort_unstable();
lines.dedup();
}
touched.probe.retain(|(_, text)| !text.trim().is_empty());
touched.probe.truncate(PROBE_LINES);
Ok(touched)
}
}