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_file(&self) -> Option<&Path> {
35 self.delta()?.new_file().path()
36 }
37
38 pub fn status(&self) -> Option<Delta> {
41 Some(self.delta()?.status())
42 }
43
44 fn delta(&self) -> Option<DiffDelta<'_>> {
46 self.diff.get_delta(self.n)
47 }
48
49 #[allow(dead_code)]
52 fn patch(&self) -> Result<Option<&Patch<'_>>, Error> {
53 let patch = Patch::from_diff(self.diff, self.n)?;
54 Ok(self.cache.get_or_init(|| patch).as_ref())
55 }
56}
57
58#[cfg(test)]
59mod test {
60 use super::*;
61 use crate::common::init_repo;
62
63 fn mfile_fixture<F, R>(f: F) -> R
64 where
65 F: FnOnce(&git2::Diff, &ModifiedFile) -> R,
66 {
67 let repo = init_repo();
68
69 let c1 = repo
70 .head()
71 .expect("Failed to fetch HEAD")
72 .peel_to_commit()
73 .expect("Failed to peel HEAD to commit");
74 let c2 = c1.parent(0).expect("Couldn't get parent");
75
76 let diff = repo
77 .diff_tree_to_tree(
78 Some(&c2.tree().expect("Failed to get tree")),
79 Some(&c1.tree().expect("Failed to get tree")),
80 None,
81 )
82 .expect("Failed to make diff");
83
84 let mfile = ModifiedFile::new(&diff, 0);
85
86 f(&diff, &mfile)
87 }
88
89 #[test]
90 fn test_old_path() {
91 mfile_fixture(|_, mfile| {
92 assert_eq!(mfile.old_path().unwrap(), "file.txt");
94 });
95 }
96
97 #[test]
98 fn test_new_path() {
99 mfile_fixture(|_, mfile| {
100 assert_eq!(mfile.new_file().unwrap(), "file.txt");
102 });
103 }
104
105 #[test]
106 fn test_delta() {
107 mfile_fixture(|_, mfile| {
108 assert_eq!(mfile.new_file().unwrap(), "file.txt");
110 });
111 }
112}