1use git2::{
2 Oid, Repository, StashApplyOptions, StashFlags,
3 build::CheckoutBuilder,
4};
5use scopetime::scope_time;
6
7use super::{CommitId, RepoPath};
8use crate::{
9 error::{Error, Result},
10 sync::repository::repo,
11};
12
13pub fn get_stashes(repo_path: &RepoPath) -> Result<Vec<CommitId>> {
15 scope_time!("get_stashes");
16
17 let mut repo = repo(repo_path)?;
18 let mut list = Vec::new();
19 repo.stash_foreach(|_index, _msg, id| {
20 list.push((*id).into());
21 true
22 })?;
23
24 Ok(list)
25}
26
27pub fn stash_drop(
29 repo_path: &RepoPath,
30 stash_id: CommitId,
31) -> Result<()> {
32 scope_time!("stash_drop");
33
34 let mut repo = repo(repo_path)?;
35
36 let index = get_stash_index(&mut repo, stash_id.into())?;
37
38 repo.stash_drop(index)?;
39
40 Ok(())
41}
42
43pub fn stash_pop(
45 repo_path: &RepoPath,
46 stash_id: CommitId,
47) -> Result<()> {
48 scope_time!("stash_pop");
49
50 let mut repo = repo(repo_path)?;
51
52 let index = get_stash_index(&mut repo, stash_id.into())?;
53
54 repo.stash_pop(index, None)?;
55
56 Ok(())
57}
58
59pub fn stash_apply(
61 repo_path: &RepoPath,
62 stash_id: CommitId,
63 allow_conflicts: bool,
64) -> Result<()> {
65 scope_time!("stash_apply");
66
67 let mut repo = repo(repo_path)?;
68
69 let index = get_stash_index(&mut repo, stash_id.get_oid())?;
70
71 let mut checkout = CheckoutBuilder::new();
72 checkout.allow_conflicts(allow_conflicts);
73
74 let mut opt = StashApplyOptions::default();
75 opt.checkout_options(checkout);
76 repo.stash_apply(index, Some(&mut opt))?;
77
78 Ok(())
79}
80
81fn get_stash_index(
82 repo: &mut Repository,
83 stash_id: Oid,
84) -> Result<usize> {
85 let mut idx = None;
86
87 repo.stash_foreach(|index, _msg, id| {
88 if *id == stash_id {
89 idx = Some(index);
90 false
91 } else {
92 true
93 }
94 })?;
95
96 idx.ok_or_else(|| {
97 Error::Generic("stash commit not found".to_string())
98 })
99}
100
101pub fn stash_save(
103 repo_path: &RepoPath,
104 message: Option<&str>,
105 include_untracked: bool,
106 keep_index: bool,
107) -> Result<CommitId> {
108 scope_time!("stash_save");
109
110 let mut repo = repo(repo_path)?;
111
112 let sig = repo.signature()?;
113
114 let mut options = StashFlags::DEFAULT;
115
116 if include_untracked {
117 options.insert(StashFlags::INCLUDE_UNTRACKED);
118 }
119 if keep_index {
120 options.insert(StashFlags::KEEP_INDEX);
121 }
122
123 let id = repo.stash_save2(&sig, message, Some(options))?;
124
125 Ok(CommitId::new(id))
126}
127
128#[cfg(test)]
129mod tests {
130 use std::{fs::File, io::Write, path::Path};
131
132 use super::*;
133 use crate::sync::{
134 commit, get_commit_files, get_commits_info, stage_add_file,
135 tests::{
136 debug_cmd_print, get_statuses, repo_init,
137 write_commit_file,
138 },
139 utils::{repo_read_file, repo_write_file},
140 };
141
142 #[test]
143 fn test_smoke() {
144 let (_td, repo) = repo_init().unwrap();
145 let root = repo.path().parent().unwrap();
146 let repo_path: &RepoPath =
147 &root.as_os_str().to_str().unwrap().into();
148
149 assert_eq!(
150 stash_save(repo_path, None, true, false).is_ok(),
151 false
152 );
153
154 assert_eq!(get_stashes(repo_path).unwrap().is_empty(), true);
155 }
156
157 #[test]
158 fn test_stashing() -> Result<()> {
159 let (_td, repo) = repo_init().unwrap();
160 let root = repo.path().parent().unwrap();
161 let repo_path: &RepoPath =
162 &root.as_os_str().to_str().unwrap().into();
163
164 File::create(root.join("foo.txt"))?
165 .write_all(b"test\nfoo")?;
166
167 assert_eq!(get_statuses(repo_path), (1, 0));
168
169 stash_save(repo_path, None, true, false)?;
170
171 assert_eq!(get_statuses(repo_path), (0, 0));
172
173 Ok(())
174 }
175
176 #[test]
177 fn test_stashes() -> Result<()> {
178 let (_td, repo) = repo_init().unwrap();
179 let root = repo.path().parent().unwrap();
180 let repo_path: &RepoPath =
181 &root.as_os_str().to_str().unwrap().into();
182
183 File::create(root.join("foo.txt"))?
184 .write_all(b"test\nfoo")?;
185
186 stash_save(repo_path, Some("foo"), true, false)?;
187
188 let res = get_stashes(repo_path)?;
189
190 assert_eq!(res.len(), 1);
191
192 let infos =
193 get_commits_info(repo_path, &[res[0]], 100).unwrap();
194
195 assert_eq!(infos[0].message, "On master: foo");
196
197 Ok(())
198 }
199
200 #[test]
201 fn test_stash_nothing_untracked() -> Result<()> {
202 let (_td, repo) = repo_init().unwrap();
203 let root = repo.path().parent().unwrap();
204 let repo_path: &RepoPath =
205 &root.as_os_str().to_str().unwrap().into();
206
207 File::create(root.join("foo.txt"))?
208 .write_all(b"test\nfoo")?;
209
210 assert!(
211 stash_save(repo_path, Some("foo"), false, false).is_err()
212 );
213
214 Ok(())
215 }
216
217 #[test]
218 fn test_stash_without_second_parent() -> Result<()> {
219 let file_path1 = Path::new("file1.txt");
220 let (_td, repo) = repo_init()?;
221 let root = repo.path().parent().unwrap();
222 let repo_path: &RepoPath =
223 &root.as_os_str().to_str().unwrap().into();
224
225 File::create(root.join(file_path1))?.write_all(b"test")?;
226 stage_add_file(repo_path, file_path1)?;
227 commit(repo_path, "c1")?;
228
229 File::create(root.join(file_path1))?
230 .write_all(b"modified")?;
231
232 debug_cmd_print(repo_path, "git stash");
236
237 let stash = get_stashes(repo_path)?[0];
238
239 let diff = get_commit_files(repo_path, stash, None)?;
240
241 assert_eq!(diff.len(), 1);
242
243 Ok(())
244 }
245
246 #[test]
247 fn test_stash_apply_conflict() {
248 let (_td, repo) = repo_init().unwrap();
249 let root = repo.path().parent().unwrap();
250 let repo_path: &RepoPath =
251 &root.as_os_str().to_str().unwrap().into();
252
253 repo_write_file(&repo, "test.txt", "test").unwrap();
254
255 let id =
256 stash_save(repo_path, Some("foo"), true, false).unwrap();
257
258 repo_write_file(&repo, "test.txt", "foo").unwrap();
259
260 let res = stash_apply(repo_path, id, false);
261
262 assert!(res.is_err());
263 }
264
265 #[test]
266 fn test_stash_apply_conflict2() {
267 let (_td, repo) = repo_init().unwrap();
268 let root = repo.path().parent().unwrap();
269 let repo_path: &RepoPath =
270 &root.as_os_str().to_str().unwrap().into();
271
272 write_commit_file(&repo, "test.txt", "test", "c1");
273
274 repo_write_file(&repo, "test.txt", "test2").unwrap();
275
276 let id =
277 stash_save(repo_path, Some("foo"), true, false).unwrap();
278
279 repo_write_file(&repo, "test.txt", "test3").unwrap();
280
281 let res = stash_apply(repo_path, id, false);
282
283 assert!(res.is_err());
284 }
285
286 #[test]
287 fn test_stash_apply_creating_conflict() {
288 let (_td, repo) = repo_init().unwrap();
289 let root = repo.path().parent().unwrap();
290 let repo_path: &RepoPath =
291 &root.as_os_str().to_str().unwrap().into();
292
293 write_commit_file(&repo, "test.txt", "test", "c1");
294
295 repo_write_file(&repo, "test.txt", "test2").unwrap();
296
297 let id =
298 stash_save(repo_path, Some("foo"), true, false).unwrap();
299
300 repo_write_file(&repo, "test.txt", "test3").unwrap();
301
302 let res = stash_apply(repo_path, id, false);
303
304 assert!(res.is_err());
305
306 let res = stash_apply(repo_path, id, true);
307
308 assert!(res.is_ok());
309 }
310
311 #[test]
312 fn test_stash_pop_no_conflict() {
313 let (_td, repo) = repo_init().unwrap();
314 let root = repo.path().parent().unwrap();
315 let repo_path: &RepoPath =
316 &root.as_os_str().to_str().unwrap().into();
317
318 write_commit_file(&repo, "test.txt", "test", "c1");
319
320 repo_write_file(&repo, "test.txt", "test2").unwrap();
321
322 let id =
323 stash_save(repo_path, Some("foo"), true, false).unwrap();
324
325 let res = stash_pop(repo_path, id);
326
327 assert!(res.is_ok());
328 assert_eq!(
329 repo_read_file(&repo, "test.txt").unwrap(),
330 "test2"
331 );
332 }
333
334 #[test]
335 fn test_stash_pop_conflict() {
336 let (_td, repo) = repo_init().unwrap();
337 let root = repo.path().parent().unwrap();
338 let repo_path: &RepoPath =
339 &root.as_os_str().to_str().unwrap().into();
340
341 repo_write_file(&repo, "test.txt", "test").unwrap();
342
343 let id =
344 stash_save(repo_path, Some("foo"), true, false).unwrap();
345
346 repo_write_file(&repo, "test.txt", "test2").unwrap();
347
348 let res = stash_pop(repo_path, id);
349
350 assert!(res.is_err());
351 assert_eq!(
352 repo_read_file(&repo, "test.txt").unwrap(),
353 "test2"
354 );
355 }
356
357 #[test]
358 fn test_stash_pop_conflict_after_commit() {
359 let (_td, repo) = repo_init().unwrap();
360 let root = repo.path().parent().unwrap();
361 let repo_path: &RepoPath =
362 &root.as_os_str().to_str().unwrap().into();
363
364 write_commit_file(&repo, "test.txt", "test", "c1");
365
366 repo_write_file(&repo, "test.txt", "test2").unwrap();
367
368 let id =
369 stash_save(repo_path, Some("foo"), true, false).unwrap();
370
371 repo_write_file(&repo, "test.txt", "test3").unwrap();
372
373 let res = stash_pop(repo_path, id);
374
375 assert!(res.is_err());
376 assert_eq!(
377 repo_read_file(&repo, "test.txt").unwrap(),
378 "test3"
379 );
380 }
381}