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