1 use crate::build::CheckoutBuilder;
2 use crate::util::Binding;
3 use crate::{panic, raw, Oid, StashApplyProgress};
4 use libc::{c_char, c_int, c_void, size_t};
5 use std::ffi::CStr;
6 use std::mem;
7 
8 /// Stash application progress notification function.
9 ///
10 /// Return `true` to continue processing, or `false` to
11 /// abort the stash application.
12 pub type StashApplyProgressCb<'a> = dyn FnMut(StashApplyProgress) -> bool + 'a;
13 
14 /// This is a callback function you can provide to iterate over all the
15 /// stashed states that will be invoked per entry.
16 pub type StashCb<'a> = dyn FnMut(usize, &str, &Oid) -> bool + 'a;
17 
18 #[allow(unused)]
19 /// Stash application options structure
20 pub struct StashApplyOptions<'cb> {
21     progress: Option<Box<StashApplyProgressCb<'cb>>>,
22     checkout_options: Option<CheckoutBuilder<'cb>>,
23     raw_opts: raw::git_stash_apply_options,
24 }
25 
26 impl<'cb> Default for StashApplyOptions<'cb> {
default() -> Self27     fn default() -> Self {
28         Self::new()
29     }
30 }
31 
32 impl<'cb> StashApplyOptions<'cb> {
33     /// Creates a default set of merge options.
new() -> StashApplyOptions<'cb>34     pub fn new() -> StashApplyOptions<'cb> {
35         let mut opts = StashApplyOptions {
36             progress: None,
37             checkout_options: None,
38             raw_opts: unsafe { mem::zeroed() },
39         };
40         assert_eq!(
41             unsafe { raw::git_stash_apply_init_options(&mut opts.raw_opts, 1) },
42             0
43         );
44         opts
45     }
46 
47     /// Set stash application flag to GIT_STASH_APPLY_REINSTATE_INDEX
reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb>48     pub fn reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb> {
49         self.raw_opts.flags = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32;
50         self
51     }
52 
53     /// Options to use when writing files to the working directory
checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb>54     pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb> {
55         self.checkout_options = Some(opts);
56         self
57     }
58 
59     /// Optional callback to notify the consumer of application progress.
60     ///
61     /// Return `true` to continue processing, or `false` to
62     /// abort the stash application.
progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb> where C: FnMut(StashApplyProgress) -> bool + 'cb,63     pub fn progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb>
64     where
65         C: FnMut(StashApplyProgress) -> bool + 'cb,
66     {
67         self.progress = Some(Box::new(callback) as Box<StashApplyProgressCb<'cb>>);
68         self.raw_opts.progress_cb = Some(stash_apply_progress_cb);
69         self.raw_opts.progress_payload = self as *mut _ as *mut _;
70         self
71     }
72 
73     /// Pointer to a raw git_stash_apply_options
raw(&mut self) -> &raw::git_stash_apply_options74     pub fn raw(&mut self) -> &raw::git_stash_apply_options {
75         unsafe {
76             if let Some(opts) = self.checkout_options.as_mut() {
77                 opts.configure(&mut self.raw_opts.checkout_options);
78             }
79         }
80         &self.raw_opts
81     }
82 }
83 
84 #[allow(unused)]
85 pub struct StashCbData<'a> {
86     pub callback: &'a mut StashCb<'a>,
87 }
88 
89 #[allow(unused)]
stash_cb( index: size_t, message: *const c_char, stash_id: *const raw::git_oid, payload: *mut c_void, ) -> c_int90 pub extern "C" fn stash_cb(
91     index: size_t,
92     message: *const c_char,
93     stash_id: *const raw::git_oid,
94     payload: *mut c_void,
95 ) -> c_int {
96     panic::wrap(|| unsafe {
97         let mut data = &mut *(payload as *mut StashCbData<'_>);
98         let res = {
99             let mut callback = &mut data.callback;
100             callback(
101                 index,
102                 CStr::from_ptr(message).to_str().unwrap(),
103                 &Binding::from_raw(stash_id),
104             )
105         };
106 
107         if res {
108             0
109         } else {
110             1
111         }
112     })
113     .unwrap_or(1)
114 }
115 
convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress116 fn convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress {
117     match progress {
118         raw::GIT_STASH_APPLY_PROGRESS_NONE => StashApplyProgress::None,
119         raw::GIT_STASH_APPLY_PROGRESS_LOADING_STASH => StashApplyProgress::LoadingStash,
120         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX => StashApplyProgress::AnalyzeIndex,
121         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED => StashApplyProgress::AnalyzeModified,
122         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED => StashApplyProgress::AnalyzeUntracked,
123         raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED => StashApplyProgress::CheckoutUntracked,
124         raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED => StashApplyProgress::CheckoutModified,
125         raw::GIT_STASH_APPLY_PROGRESS_DONE => StashApplyProgress::Done,
126 
127         _ => StashApplyProgress::None,
128     }
129 }
130 
131 #[allow(unused)]
stash_apply_progress_cb( progress: raw::git_stash_apply_progress_t, payload: *mut c_void, ) -> c_int132 extern "C" fn stash_apply_progress_cb(
133     progress: raw::git_stash_apply_progress_t,
134     payload: *mut c_void,
135 ) -> c_int {
136     panic::wrap(|| unsafe {
137         let mut options = &mut *(payload as *mut StashApplyOptions<'_>);
138         let res = {
139             let mut callback = options.progress.as_mut().unwrap();
140             callback(convert_progress(progress))
141         };
142 
143         if res {
144             0
145         } else {
146             -1
147         }
148     })
149     .unwrap_or(-1)
150 }
151 
152 #[cfg(test)]
153 mod tests {
154     use crate::stash::StashApplyOptions;
155     use crate::test::repo_init;
156     use crate::{Repository, StashFlags, Status};
157     use std::fs;
158     use std::io::Write;
159     use std::path::Path;
160 
make_stash<C>(next: C) where C: FnOnce(&mut Repository),161     fn make_stash<C>(next: C)
162     where
163         C: FnOnce(&mut Repository),
164     {
165         let (_td, mut repo) = repo_init();
166         let signature = repo.signature().unwrap();
167 
168         let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
169         println!("using path {:?}", p);
170         fs::File::create(&p)
171             .unwrap()
172             .write("data".as_bytes())
173             .unwrap();
174 
175         let rel_p = Path::new("file_b.txt");
176         assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW);
177 
178         repo.stash_save(&signature, "msg1", Some(StashFlags::INCLUDE_UNTRACKED))
179             .unwrap();
180 
181         assert!(repo.status_file(&rel_p).is_err());
182 
183         let mut count = 0;
184         repo.stash_foreach(|index, name, _oid| {
185             count += 1;
186             assert!(index == 0);
187             assert!(name == "On main: msg1");
188             true
189         })
190         .unwrap();
191 
192         assert!(count == 1);
193         next(&mut repo);
194     }
195 
count_stash(repo: &mut Repository) -> usize196     fn count_stash(repo: &mut Repository) -> usize {
197         let mut count = 0;
198         repo.stash_foreach(|_, _, _| {
199             count += 1;
200             true
201         })
202         .unwrap();
203         count
204     }
205 
206     #[test]
smoke_stash_save_drop()207     fn smoke_stash_save_drop() {
208         make_stash(|repo| {
209             repo.stash_drop(0).unwrap();
210             assert!(count_stash(repo) == 0)
211         })
212     }
213 
214     #[test]
smoke_stash_save_pop()215     fn smoke_stash_save_pop() {
216         make_stash(|repo| {
217             repo.stash_pop(0, None).unwrap();
218             assert!(count_stash(repo) == 0)
219         })
220     }
221 
222     #[test]
smoke_stash_save_apply()223     fn smoke_stash_save_apply() {
224         make_stash(|repo| {
225             let mut options = StashApplyOptions::new();
226             options.progress_cb(|progress| {
227                 println!("{:?}", progress);
228                 true
229             });
230 
231             repo.stash_apply(0, Some(&mut options)).unwrap();
232             assert!(count_stash(repo) == 1)
233         })
234     }
235 
236     #[test]
test_stash_save2_msg_none()237     fn test_stash_save2_msg_none() {
238         let (_td, mut repo) = repo_init();
239         let signature = repo.signature().unwrap();
240 
241         let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
242 
243         fs::File::create(&p)
244             .unwrap()
245             .write("data".as_bytes())
246             .unwrap();
247 
248         repo.stash_save2(&signature, None, Some(StashFlags::INCLUDE_UNTRACKED))
249             .unwrap();
250 
251         let mut stash_name = String::new();
252         repo.stash_foreach(|index, name, _oid| {
253             assert_eq!(index, 0);
254             stash_name = name.to_string();
255             true
256         })
257         .unwrap();
258 
259         assert!(stash_name.starts_with("WIP on main:"));
260     }
261 }
262