use git2::{Delta, DiffDelta, Patch};
use lru::LruCache;
use std::{num::NonZeroUsize, path::Path, rc::Rc, sync::Mutex};
use crate::Error;
use crate::domain::{CachedPatch, RcDiff, RcPatch};
const CACHE_KEY: u8 = 1;
pub struct ModifiedFile<'c> {
diff: RcDiff<'c>,
n: usize,
patch_cache: Mutex<CachedPatch<'c>>,
}
impl<'c> ModifiedFile<'c> {
pub fn new(diff: RcDiff<'c>, n: usize) -> Self {
let patch_cache = Mutex::new(LruCache::new(NonZeroUsize::new(1).unwrap()));
ModifiedFile {
diff,
n,
patch_cache,
}
}
pub fn old_path(&self) -> Option<&Path> {
self.delta()?.old_file().path()
}
pub fn new_path(&self) -> Option<&Path> {
self.delta()?.new_file().path()
}
pub fn filename(&self) -> Option<&str> {
let dev_null = Path::new("/dev/null");
let path = match self.new_path() {
Some(p) if p != dev_null => p,
_ => self.old_path()?,
};
path.file_name()?.to_str()
}
pub fn status(&self) -> Option<Delta> {
Some(self.delta()?.status())
}
pub fn insertions(&self) -> Result<usize, Error> {
Ok(match self.patch()? {
Some(p) => p.line_stats()?.1,
None => 0,
})
}
pub fn deletions(&self) -> Result<usize, Error> {
Ok(match self.patch()? {
Some(p) => p.line_stats()?.2,
None => 0,
})
}
fn delta(&self) -> Option<DiffDelta<'_>> {
self.diff.get_delta(self.n)
}
fn patch(&self) -> Result<Option<RcPatch<'c>>, Error> {
let mut guard = self.patch_cache.lock().map_err(|_| Error::PoisonedCache)?;
let data = guard.try_get_or_insert(CACHE_KEY, || {
let patch = Patch::from_diff(self.diff.as_ref(), self.n)?;
match patch {
Some(p) => Ok::<Option<RcPatch>, Error>(Some(Rc::new(p))),
None => Ok(None),
}
})?;
Ok(data.as_ref().map(Rc::clone))
}
}
#[cfg(test)]
mod test {
use std::rc::Rc;
use super::*;
use crate::common::init_repo;
fn mfile_fixture<F, R>(f: F) -> R
where
F: FnOnce(&ModifiedFile) -> R,
{
let repo = init_repo();
let c1 = repo
.head()
.expect("Failed to fetch HEAD")
.peel_to_commit()
.expect("Failed to peel HEAD to commit");
let c2 = c1.parent(0).expect("Couldn't get parent");
let diff = repo
.diff_tree_to_tree(
Some(&c2.tree().expect("Failed to get tree")),
Some(&c1.tree().expect("Failed to get tree")),
None,
)
.expect("Failed to make diff");
let diff = Rc::new(diff);
let mfile = ModifiedFile::new(diff, 0);
f(&mfile)
}
#[test]
fn test_old_path() {
mfile_fixture(|mfile| {
assert_eq!(mfile.old_path().unwrap(), "file.txt");
});
}
#[test]
fn test_new_path() {
mfile_fixture(|mfile| {
assert_eq!(mfile.new_path().unwrap(), "file.txt");
});
}
#[test]
fn test_delta() {
mfile_fixture(|mfile| {
assert_eq!(mfile.new_path().unwrap(), "file.txt");
});
}
}