1 use super::{utils::repo, CommitId};
2 use crate::error::{Error, Result};
3 use git2::{Oid, Repository, StashFlags};
4 use scopetime::scope_time;
5 
6 ///
get_stashes(repo_path: &str) -> Result<Vec<CommitId>>7 pub fn get_stashes(repo_path: &str) -> Result<Vec<CommitId>> {
8     scope_time!("get_stashes");
9 
10     let mut repo = repo(repo_path)?;
11 
12     let mut list = Vec::new();
13 
14     repo.stash_foreach(|_index, _msg, id| {
15         list.push((*id).into());
16         true
17     })?;
18 
19     Ok(list)
20 }
21 
22 /// checks whether a given commit is a stash commit.
is_stash_commit( repo_path: &str, id: &CommitId, ) -> Result<bool>23 pub fn is_stash_commit(
24     repo_path: &str,
25     id: &CommitId,
26 ) -> Result<bool> {
27     let stashes = get_stashes(repo_path)?;
28     Ok(stashes.contains(&id))
29 }
30 
31 ///
stash_drop(repo_path: &str, stash_id: CommitId) -> Result<()>32 pub fn stash_drop(repo_path: &str, stash_id: CommitId) -> Result<()> {
33     scope_time!("stash_drop");
34 
35     let mut repo = repo(repo_path)?;
36 
37     let index = get_stash_index(&mut repo, stash_id.into())?;
38 
39     repo.stash_drop(index)?;
40 
41     Ok(())
42 }
43 
44 ///
stash_apply( repo_path: &str, stash_id: CommitId, ) -> Result<()>45 pub fn stash_apply(
46     repo_path: &str,
47     stash_id: CommitId,
48 ) -> Result<()> {
49     scope_time!("stash_apply");
50 
51     let mut repo = repo(repo_path)?;
52 
53     let index = get_stash_index(&mut repo, stash_id.get_oid())?;
54 
55     repo.stash_apply(index, None)?;
56 
57     Ok(())
58 }
59 
get_stash_index( repo: &mut Repository, stash_id: Oid, ) -> Result<usize>60 fn get_stash_index(
61     repo: &mut Repository,
62     stash_id: Oid,
63 ) -> Result<usize> {
64     let mut idx = None;
65 
66     repo.stash_foreach(|index, _msg, id| {
67         if *id == stash_id {
68             idx = Some(index);
69             false
70         } else {
71             true
72         }
73     })?;
74 
75     idx.ok_or_else(|| {
76         Error::Generic("stash commit not found".to_string())
77     })
78 }
79 
80 ///
stash_save( repo_path: &str, message: Option<&str>, include_untracked: bool, keep_index: bool, ) -> Result<CommitId>81 pub fn stash_save(
82     repo_path: &str,
83     message: Option<&str>,
84     include_untracked: bool,
85     keep_index: bool,
86 ) -> Result<CommitId> {
87     scope_time!("stash_save");
88 
89     let mut repo = repo(repo_path)?;
90 
91     let sig = repo.signature()?;
92 
93     let mut options = StashFlags::DEFAULT;
94 
95     if include_untracked {
96         options.insert(StashFlags::INCLUDE_UNTRACKED);
97     }
98     if keep_index {
99         options.insert(StashFlags::KEEP_INDEX)
100     }
101 
102     let id = repo.stash_save2(&sig, message, Some(options))?;
103 
104     Ok(CommitId::new(id))
105 }
106 
107 #[cfg(test)]
108 mod tests {
109     use super::*;
110     use crate::sync::{
111         commit, get_commit_files, get_commits_info, stage_add_file,
112         tests::{debug_cmd_print, get_statuses, repo_init},
113     };
114     use std::{fs::File, io::Write, path::Path};
115 
116     #[test]
test_smoke()117     fn test_smoke() {
118         let (_td, repo) = repo_init().unwrap();
119         let root = repo.path().parent().unwrap();
120         let repo_path = root.as_os_str().to_str().unwrap();
121 
122         assert_eq!(
123             stash_save(repo_path, None, true, false).is_ok(),
124             false
125         );
126 
127         assert_eq!(get_stashes(repo_path).unwrap().is_empty(), true);
128     }
129 
130     #[test]
test_stashing() -> Result<()>131     fn test_stashing() -> Result<()> {
132         let (_td, repo) = repo_init().unwrap();
133         let root = repo.path().parent().unwrap();
134         let repo_path = root.as_os_str().to_str().unwrap();
135 
136         File::create(&root.join("foo.txt"))?
137             .write_all(b"test\nfoo")?;
138 
139         assert_eq!(get_statuses(repo_path), (1, 0));
140 
141         stash_save(repo_path, None, true, false)?;
142 
143         assert_eq!(get_statuses(repo_path), (0, 0));
144 
145         Ok(())
146     }
147 
148     #[test]
test_stashes() -> Result<()>149     fn test_stashes() -> Result<()> {
150         let (_td, repo) = repo_init().unwrap();
151         let root = repo.path().parent().unwrap();
152         let repo_path = root.as_os_str().to_str().unwrap();
153 
154         File::create(&root.join("foo.txt"))?
155             .write_all(b"test\nfoo")?;
156 
157         stash_save(repo_path, Some("foo"), true, false)?;
158 
159         let res = get_stashes(repo_path)?;
160 
161         assert_eq!(res.len(), 1);
162 
163         let infos =
164             get_commits_info(repo_path, &[res[0]], 100).unwrap();
165 
166         assert_eq!(infos[0].message, "On master: foo");
167 
168         Ok(())
169     }
170 
171     #[test]
test_stash_nothing_untracked() -> Result<()>172     fn test_stash_nothing_untracked() -> Result<()> {
173         let (_td, repo) = repo_init().unwrap();
174         let root = repo.path().parent().unwrap();
175         let repo_path = root.as_os_str().to_str().unwrap();
176 
177         File::create(&root.join("foo.txt"))?
178             .write_all(b"test\nfoo")?;
179 
180         assert!(
181             stash_save(repo_path, Some("foo"), false, false).is_err()
182         );
183 
184         Ok(())
185     }
186 
187     #[test]
test_stash_without_2nd_parent() -> Result<()>188     fn test_stash_without_2nd_parent() -> Result<()> {
189         let file_path1 = Path::new("file1.txt");
190         let (_td, repo) = repo_init()?;
191         let root = repo.path().parent().unwrap();
192         let repo_path = root.as_os_str().to_str().unwrap();
193 
194         File::create(&root.join(file_path1))?.write_all(b"test")?;
195         stage_add_file(repo_path, file_path1)?;
196         commit(repo_path, "c1")?;
197 
198         File::create(&root.join(file_path1))?
199             .write_all(b"modified")?;
200 
201         //NOTE: apparently `libgit2` works differently to git stash in
202         //always creating the third parent for untracked files while the
203         //cli skips that step when no new files exist
204         debug_cmd_print(repo_path, "git stash");
205 
206         let stash = get_stashes(repo_path)?[0];
207 
208         let diff = get_commit_files(repo_path, stash)?;
209 
210         assert_eq!(diff.len(), 1);
211 
212         Ok(())
213     }
214 }
215