1 use {raw, panic, Oid, StashApplyProgress};
2 use std::ffi::{CStr};
3 use util::{Binding};
4 use libc::{c_int, c_char, size_t, c_void};
5 use build::{CheckoutBuilder};
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> = 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> = 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!(unsafe {
41             raw::git_stash_apply_init_options(&mut opts.raw_opts, 1)
42         }, 0);
43         opts
44     }
45 
46     /// Set stash application flag to GIT_STASH_APPLY_REINSTATE_INDEX
reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb>47     pub fn reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb> {
48         self.raw_opts.flags = raw::GIT_STASH_APPLY_REINSTATE_INDEX;
49         self
50     }
51 
52     /// Options to use when writing files to the working directory
checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb>53     pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb> {
54         self.checkout_options = Some(opts);
55         self
56     }
57 
58     /// Optional callback to notify the consumer of application progress.
59     ///
60     /// Return `true` to continue processing, or `false` to
61     /// abort the stash application.
progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb> where C: FnMut(StashApplyProgress) -> bool + 'cb62     pub fn progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb>
63         where C: FnMut(StashApplyProgress) -> bool + 'cb
64     {
65         self.progress = Some(Box::new(callback) as Box<StashApplyProgressCb<'cb>>);
66         self.raw_opts.progress_cb = stash_apply_progress_cb;
67         self.raw_opts.progress_payload = self as *mut _ as *mut _;
68         self
69     }
70 
71     /// Pointer to a raw git_stash_apply_options
raw(&mut self) -> &raw::git_stash_apply_options72     pub fn raw(&mut self) -> &raw::git_stash_apply_options {
73         unsafe {
74             if let Some(opts) = self.checkout_options.as_mut() {
75                 opts.configure(&mut self.raw_opts.checkout_options);
76             }
77         }
78         &self.raw_opts
79     }
80 }
81 
82 #[allow(unused)]
83 pub struct StashCbData<'a> {
84     pub callback: &'a mut StashCb<'a>
85 }
86 
87 #[allow(unused)]
stash_cb(index: size_t, message: *const c_char, stash_id: *const raw::git_oid, payload: *mut c_void) -> c_int88 pub extern fn stash_cb(index: size_t,
89                         message: *const c_char,
90                         stash_id: *const raw::git_oid,
91                         payload: *mut c_void)
92                         -> c_int
93 {
94     panic::wrap(|| unsafe {
95         let mut data = &mut *(payload as *mut StashCbData);
96         let res = {
97             let mut callback = &mut data.callback;
98             callback(index,
99                      CStr::from_ptr(message).to_str().unwrap(),
100                      &Binding::from_raw(stash_id))
101         };
102 
103         if res { 0 } else { 1 }
104     }).unwrap_or(1)
105 }
106 
convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress107 fn convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress {
108     match progress {
109         raw::GIT_STASH_APPLY_PROGRESS_NONE => StashApplyProgress::None,
110         raw::GIT_STASH_APPLY_PROGRESS_LOADING_STASH => StashApplyProgress::LoadingStash,
111         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX => StashApplyProgress::AnalyzeIndex,
112         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED => StashApplyProgress::AnalyzeModified,
113         raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED => StashApplyProgress::AnalyzeUntracked,
114         raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED => StashApplyProgress::CheckoutUntracked,
115         raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED => StashApplyProgress::CheckoutModified,
116         raw::GIT_STASH_APPLY_PROGRESS_DONE => StashApplyProgress::Done,
117 
118         _ => StashApplyProgress::None
119     }
120 }
121 
122 #[allow(unused)]
stash_apply_progress_cb(progress: raw::git_stash_apply_progress_t, payload: *mut c_void) -> c_int123 extern fn stash_apply_progress_cb(progress: raw::git_stash_apply_progress_t,
124                                   payload: *mut c_void)
125                                   -> c_int
126 {
127     panic::wrap(|| unsafe {
128         let mut options = &mut *(payload as *mut StashApplyOptions);
129         let res = {
130             let mut callback = options.progress.as_mut().unwrap();
131             callback(convert_progress(progress))
132         };
133 
134         if res { 0 } else { -1 }
135     }).unwrap_or(-1)
136 }
137 
138 #[cfg(test)]
139 mod tests {
140     use stash::{StashApplyOptions};
141     use std::io::{Write};
142     use std::fs;
143     use std::path::Path;
144     use test::{repo_init};
145     use {Repository, Status, StashFlags};
146 
make_stash<C>(next: C) where C: FnOnce(&mut Repository)147     fn make_stash<C>(next: C) where C: FnOnce(&mut Repository) {
148         let (_td, mut repo) = repo_init();
149         let signature = repo.signature().unwrap();
150 
151         let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
152         println!("using path {:?}", p);
153         fs::File::create(&p).unwrap()
154             .write("data".as_bytes()).unwrap();
155 
156         let rel_p = Path::new("file_b.txt");
157         assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW);
158 
159         repo.stash_save(&signature, "msg1", Some(StashFlags::INCLUDE_UNTRACKED)).unwrap();
160 
161         assert!(repo.status_file(&rel_p).is_err());
162 
163         let mut count = 0;
164         repo.stash_foreach(|index, name, _oid| {
165             count += 1;
166             assert!(index == 0);
167             assert!(name == "On master: msg1");
168             true
169         }).unwrap();
170 
171         assert!(count == 1);
172         next(&mut repo);
173     }
174 
count_stash(repo: &mut Repository) -> usize175     fn count_stash(repo: &mut Repository) -> usize {
176         let mut count = 0;
177         repo.stash_foreach(|_, _, _| { count += 1; true }).unwrap();
178         count
179     }
180 
181     #[test]
smoke_stash_save_drop()182     fn smoke_stash_save_drop() {
183         make_stash(|repo| {
184             repo.stash_drop(0).unwrap();
185             assert!(count_stash(repo) == 0)
186         })
187     }
188 
189     #[test]
smoke_stash_save_pop()190     fn smoke_stash_save_pop() {
191         make_stash(|repo| {
192             repo.stash_pop(0, None).unwrap();
193             assert!(count_stash(repo) == 0)
194         })
195     }
196 
197     #[test]
smoke_stash_save_apply()198     fn smoke_stash_save_apply() {
199         make_stash(|repo| {
200             let mut options = StashApplyOptions::new();
201             options.progress_cb(|progress| {
202                 println!("{:?}", progress);
203                 true
204             });
205 
206             repo.stash_apply(0, Some(&mut options)).unwrap();
207             assert!(count_stash(repo) == 1)
208         })
209     }
210 }
211