1 use super::{stash::is_stash_commit, utils::repo, CommitId};
2 use crate::{error::Result, StatusItem, StatusItemType};
3 use git2::{Diff, DiffDelta, DiffOptions, Repository};
4 use scopetime::scope_time;
5 
6 /// get all files that are part of a commit
get_commit_files( repo_path: &str, id: CommitId, ) -> Result<Vec<StatusItem>>7 pub fn get_commit_files(
8     repo_path: &str,
9     id: CommitId,
10 ) -> Result<Vec<StatusItem>> {
11     scope_time!("get_commit_files");
12 
13     let repo = repo(repo_path)?;
14 
15     let diff = get_commit_diff(&repo, id, None)?;
16 
17     let mut res = Vec::new();
18 
19     diff.foreach(
20         &mut |delta: DiffDelta<'_>, _progress| {
21             res.push(StatusItem {
22                 path: delta
23                     .new_file()
24                     .path()
25                     .map(|p| p.to_str().unwrap_or("").to_string())
26                     .unwrap_or_default(),
27                 status: StatusItemType::from(delta.status()),
28             });
29             true
30         },
31         None,
32         None,
33         None,
34     )?;
35 
36     Ok(res)
37 }
38 
39 ///
get_commit_diff( repo: &Repository, id: CommitId, pathspec: Option<String>, ) -> Result<Diff<'_>>40 pub(crate) fn get_commit_diff(
41     repo: &Repository,
42     id: CommitId,
43     pathspec: Option<String>,
44 ) -> Result<Diff<'_>> {
45     // scope_time!("get_commit_diff");
46 
47     let commit = repo.find_commit(id.into())?;
48     let commit_tree = commit.tree()?;
49     let parent = if commit.parent_count() > 0 {
50         Some(repo.find_commit(commit.parent_id(0)?)?.tree()?)
51     } else {
52         None
53     };
54 
55     let mut opt = pathspec.as_ref().map(|p| {
56         let mut opts = DiffOptions::new();
57         opts.pathspec(p);
58         opts.show_binary(true);
59         opts
60     });
61 
62     let mut diff = repo.diff_tree_to_tree(
63         parent.as_ref(),
64         Some(&commit_tree),
65         opt.as_mut(),
66     )?;
67 
68     if is_stash_commit(
69         repo.path().to_str().expect("repo path utf8 err"),
70         &id,
71     )? {
72         if let Ok(untracked_commit) = commit.parent_id(2) {
73             let untracked_diff = get_commit_diff(
74                 repo,
75                 CommitId::new(untracked_commit),
76                 pathspec,
77             )?;
78 
79             diff.merge(&untracked_diff)?;
80         }
81     }
82 
83     Ok(diff)
84 }
85 
86 #[cfg(test)]
87 mod tests {
88     use super::get_commit_files;
89     use crate::{
90         error::Result,
91         sync::{
92             commit, stage_add_file, stash_save,
93             tests::{get_statuses, repo_init},
94         },
95         StatusItemType,
96     };
97     use std::{fs::File, io::Write, path::Path};
98 
99     #[test]
test_smoke() -> Result<()>100     fn test_smoke() -> Result<()> {
101         let file_path = Path::new("file1.txt");
102         let (_td, repo) = repo_init()?;
103         let root = repo.path().parent().unwrap();
104         let repo_path = root.as_os_str().to_str().unwrap();
105 
106         File::create(&root.join(file_path))?
107             .write_all(b"test file1 content")?;
108 
109         stage_add_file(repo_path, file_path)?;
110 
111         let id = commit(repo_path, "commit msg")?;
112 
113         let diff = get_commit_files(repo_path, id)?;
114 
115         assert_eq!(diff.len(), 1);
116         assert_eq!(diff[0].status, StatusItemType::New);
117 
118         Ok(())
119     }
120 
121     #[test]
test_stashed_untracked() -> Result<()>122     fn test_stashed_untracked() -> Result<()> {
123         let file_path = Path::new("file1.txt");
124         let (_td, repo) = repo_init()?;
125         let root = repo.path().parent().unwrap();
126         let repo_path = root.as_os_str().to_str().unwrap();
127 
128         File::create(&root.join(file_path))?
129             .write_all(b"test file1 content")?;
130 
131         let id = stash_save(repo_path, None, true, false)?;
132 
133         let diff = get_commit_files(repo_path, id)?;
134 
135         assert_eq!(diff.len(), 1);
136         assert_eq!(diff[0].status, StatusItemType::New);
137 
138         Ok(())
139     }
140 
141     #[test]
test_stashed_untracked_and_modified() -> Result<()>142     fn test_stashed_untracked_and_modified() -> Result<()> {
143         let file_path1 = Path::new("file1.txt");
144         let file_path2 = Path::new("file2.txt");
145         let (_td, repo) = repo_init()?;
146         let root = repo.path().parent().unwrap();
147         let repo_path = root.as_os_str().to_str().unwrap();
148 
149         File::create(&root.join(file_path1))?.write_all(b"test")?;
150         stage_add_file(repo_path, file_path1)?;
151         commit(repo_path, "c1")?;
152 
153         File::create(&root.join(file_path1))?
154             .write_all(b"modified")?;
155         File::create(&root.join(file_path2))?.write_all(b"new")?;
156 
157         assert_eq!(get_statuses(repo_path), (2, 0));
158 
159         let id = stash_save(repo_path, None, true, false)?;
160 
161         let diff = get_commit_files(repo_path, id)?;
162 
163         assert_eq!(diff.len(), 2);
164         assert_eq!(diff[0].status, StatusItemType::Modified);
165         assert_eq!(diff[1].status, StatusItemType::New);
166 
167         Ok(())
168     }
169 }
170