1use git2::{Delta, Diff, DiffDelta, Patch};
2use std::{path::Path, sync::OnceLock};
3
4use crate::Error;
5
6pub struct ModifiedFile<'c> {
8 cache: OnceLock<Option<Patch<'c>>>,
9 diff: &'c Diff<'c>,
10 n: usize,
11}
12
13impl<'c> ModifiedFile<'c> {
14 pub fn new(diff: &'c Diff<'_>, n: usize) -> Self {
21 ModifiedFile {
22 cache: OnceLock::new(),
23 diff,
24 n,
25 }
26 }
27
28 pub fn old_path(&self) -> Option<&Path> {
30 self.delta()?.old_file().path()
31 }
32
33 pub fn new_path(&self) -> Option<&Path> {
35 self.delta()?.new_file().path()
36 }
37
38 pub fn filename(&self) -> Option<&str> {
42 let dev_null = Path::new("/dev/null");
43 let path = match self.new_path() {
44 Some(p) if p != dev_null => p,
45 _ => self.old_path()?,
46 };
47
48 path.file_name()?.to_str()
49 }
50
51 pub fn status(&self) -> Option<Delta> {
54 Some(self.delta()?.status())
55 }
56
57 pub fn insertions(&self) -> Result<usize, Error> {
59 Ok(match self.patch()? {
61 Some(p) => p.line_stats()?.1,
62 None => 0,
63 })
64 }
65
66 fn delta(&self) -> Option<DiffDelta<'_>> {
68 self.diff.get_delta(self.n)
69 }
70
71 #[allow(dead_code)]
76 fn patch(&self) -> Result<Option<&Patch<'_>>, Error> {
77 let patch = Patch::from_diff(self.diff, self.n)?;
78 Ok(self.cache.get_or_init(|| patch).as_ref())
79 }
80}
81
82#[cfg(test)]
83mod test {
84 use super::*;
85 use crate::common::init_repo;
86
87 fn mfile_fixture<F, R>(f: F) -> R
88 where
89 F: FnOnce(&git2::Diff, &ModifiedFile) -> R,
90 {
91 let repo = init_repo();
92
93 let c1 = repo
94 .head()
95 .expect("Failed to fetch HEAD")
96 .peel_to_commit()
97 .expect("Failed to peel HEAD to commit");
98 let c2 = c1.parent(0).expect("Couldn't get parent");
99
100 let diff = repo
101 .diff_tree_to_tree(
102 Some(&c2.tree().expect("Failed to get tree")),
103 Some(&c1.tree().expect("Failed to get tree")),
104 None,
105 )
106 .expect("Failed to make diff");
107
108 let mfile = ModifiedFile::new(&diff, 0);
109
110 f(&diff, &mfile)
111 }
112
113 #[test]
114 fn test_old_path() {
115 mfile_fixture(|_, mfile| {
116 assert_eq!(mfile.old_path().unwrap(), "file.txt");
118 });
119 }
120
121 #[test]
122 fn test_new_path() {
123 mfile_fixture(|_, mfile| {
124 assert_eq!(mfile.new_path().unwrap(), "file.txt");
126 });
127 }
128
129 #[test]
130 fn test_delta() {
131 mfile_fixture(|_, mfile| {
132 assert_eq!(mfile.new_path().unwrap(), "file.txt");
134 });
135 }
136}