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 fn delta(&self) -> Option<DiffDelta<'_>> {
59 self.diff.get_delta(self.n)
60 }
61
62 #[allow(dead_code)]
65 fn patch(&self) -> Result<Option<&Patch<'_>>, Error> {
66 let patch = Patch::from_diff(self.diff, self.n)?;
67 Ok(self.cache.get_or_init(|| patch).as_ref())
68 }
69}
70
71#[cfg(test)]
72mod test {
73 use super::*;
74 use crate::common::init_repo;
75
76 fn mfile_fixture<F, R>(f: F) -> R
77 where
78 F: FnOnce(&git2::Diff, &ModifiedFile) -> R,
79 {
80 let repo = init_repo();
81
82 let c1 = repo
83 .head()
84 .expect("Failed to fetch HEAD")
85 .peel_to_commit()
86 .expect("Failed to peel HEAD to commit");
87 let c2 = c1.parent(0).expect("Couldn't get parent");
88
89 let diff = repo
90 .diff_tree_to_tree(
91 Some(&c2.tree().expect("Failed to get tree")),
92 Some(&c1.tree().expect("Failed to get tree")),
93 None,
94 )
95 .expect("Failed to make diff");
96
97 let mfile = ModifiedFile::new(&diff, 0);
98
99 f(&diff, &mfile)
100 }
101
102 #[test]
103 fn test_old_path() {
104 mfile_fixture(|_, mfile| {
105 assert_eq!(mfile.old_path().unwrap(), "file.txt");
107 });
108 }
109
110 #[test]
111 fn test_new_path() {
112 mfile_fixture(|_, mfile| {
113 assert_eq!(mfile.new_path().unwrap(), "file.txt");
115 });
116 }
117
118 #[test]
119 fn test_delta() {
120 mfile_fixture(|_, mfile| {
121 assert_eq!(mfile.new_path().unwrap(), "file.txt");
123 });
124 }
125}