1 use libc::{c_int, c_uint, c_void};
2 use std::ffi::CString;
3 use std::marker;
4 
5 use crate::util::Binding;
6 use crate::{panic, raw, Error, Oid, Repository, Sort};
7 
8 /// A revwalk allows traversal of the commit graph defined by including one or
9 /// more leaves and excluding one or more roots.
10 pub struct Revwalk<'repo> {
11     raw: *mut raw::git_revwalk,
12     _marker: marker::PhantomData<&'repo Repository>,
13 }
14 
15 /// A `Revwalk` with an assiciated "hide callback", see `with_hide_callback`
16 pub struct RevwalkWithHideCb<'repo, 'cb, C>
17 where
18     C: FnMut(Oid) -> bool,
19 {
20     revwalk: Revwalk<'repo>,
21     _marker: marker::PhantomData<&'cb C>,
22 }
23 
revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int where C: FnMut(Oid) -> bool,24 extern "C" fn revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int
25 where
26     C: FnMut(Oid) -> bool,
27 {
28     panic::wrap(|| unsafe {
29         let hide_cb = payload as *mut C;
30         if (*hide_cb)(Oid::from_raw(commit_id)) {
31             1
32         } else {
33             0
34         }
35     })
36     .unwrap_or(-1)
37 }
38 
39 impl<'repo, 'cb, C: FnMut(Oid) -> bool> RevwalkWithHideCb<'repo, 'cb, C> {
40     /// Consumes the `RevwalkWithHideCb` and returns the contained `Revwalk`.
41     ///
42     /// Note that this will reset the `Revwalk`.
into_inner(mut self) -> Result<Revwalk<'repo>, Error>43     pub fn into_inner(mut self) -> Result<Revwalk<'repo>, Error> {
44         self.revwalk.reset()?;
45         Ok(self.revwalk)
46     }
47 }
48 
49 impl<'repo> Revwalk<'repo> {
50     /// Reset a revwalk to allow re-configuring it.
51     ///
52     /// The revwalk is automatically reset when iteration of its commits
53     /// completes.
reset(&mut self) -> Result<(), Error>54     pub fn reset(&mut self) -> Result<(), Error> {
55         unsafe {
56             try_call!(raw::git_revwalk_reset(self.raw()));
57         }
58         Ok(())
59     }
60 
61     /// Set the order in which commits are visited.
set_sorting(&mut self, sort_mode: Sort) -> Result<(), Error>62     pub fn set_sorting(&mut self, sort_mode: Sort) -> Result<(), Error> {
63         unsafe {
64             try_call!(raw::git_revwalk_sorting(
65                 self.raw(),
66                 sort_mode.bits() as c_uint
67             ));
68         }
69         Ok(())
70     }
71 
72     /// Simplify the history by first-parent
73     ///
74     /// No parents other than the first for each commit will be enqueued.
simplify_first_parent(&mut self) -> Result<(), Error>75     pub fn simplify_first_parent(&mut self) -> Result<(), Error> {
76         unsafe {
77             try_call!(raw::git_revwalk_simplify_first_parent(self.raw));
78         }
79         Ok(())
80     }
81 
82     /// Mark a commit to start traversal from.
83     ///
84     /// The given OID must belong to a committish on the walked repository.
85     ///
86     /// The given commit will be used as one of the roots when starting the
87     /// revision walk. At least one commit must be pushed onto the walker before
88     /// a walk can be started.
push(&mut self, oid: Oid) -> Result<(), Error>89     pub fn push(&mut self, oid: Oid) -> Result<(), Error> {
90         unsafe {
91             try_call!(raw::git_revwalk_push(self.raw(), oid.raw()));
92         }
93         Ok(())
94     }
95 
96     /// Push the repository's HEAD
97     ///
98     /// For more information, see `push`.
push_head(&mut self) -> Result<(), Error>99     pub fn push_head(&mut self) -> Result<(), Error> {
100         unsafe {
101             try_call!(raw::git_revwalk_push_head(self.raw()));
102         }
103         Ok(())
104     }
105 
106     /// Push matching references
107     ///
108     /// The OIDs pointed to by the references that match the given glob pattern
109     /// will be pushed to the revision walker.
110     ///
111     /// A leading 'refs/' is implied if not present as well as a trailing `/ \
112     /// *` if the glob lacks '?', ' \ *' or '['.
113     ///
114     /// Any references matching this glob which do not point to a committish
115     /// will be ignored.
push_glob(&mut self, glob: &str) -> Result<(), Error>116     pub fn push_glob(&mut self, glob: &str) -> Result<(), Error> {
117         let glob = CString::new(glob)?;
118         unsafe {
119             try_call!(raw::git_revwalk_push_glob(self.raw, glob));
120         }
121         Ok(())
122     }
123 
124     /// Push and hide the respective endpoints of the given range.
125     ///
126     /// The range should be of the form `<commit>..<commit>` where each
127     /// `<commit>` is in the form accepted by `revparse_single`. The left-hand
128     /// commit will be hidden and the right-hand commit pushed.
push_range(&mut self, range: &str) -> Result<(), Error>129     pub fn push_range(&mut self, range: &str) -> Result<(), Error> {
130         let range = CString::new(range)?;
131         unsafe {
132             try_call!(raw::git_revwalk_push_range(self.raw, range));
133         }
134         Ok(())
135     }
136 
137     /// Push the OID pointed to by a reference
138     ///
139     /// The reference must point to a committish.
push_ref(&mut self, reference: &str) -> Result<(), Error>140     pub fn push_ref(&mut self, reference: &str) -> Result<(), Error> {
141         let reference = CString::new(reference)?;
142         unsafe {
143             try_call!(raw::git_revwalk_push_ref(self.raw, reference));
144         }
145         Ok(())
146     }
147 
148     /// Mark a commit as not of interest to this revwalk.
hide(&mut self, oid: Oid) -> Result<(), Error>149     pub fn hide(&mut self, oid: Oid) -> Result<(), Error> {
150         unsafe {
151             try_call!(raw::git_revwalk_hide(self.raw(), oid.raw()));
152         }
153         Ok(())
154     }
155 
156     /// Hide all commits for which the callback returns true from
157     /// the walk.
with_hide_callback<'cb, C>( self, callback: &'cb C, ) -> Result<RevwalkWithHideCb<'repo, 'cb, C>, Error> where C: FnMut(Oid) -> bool,158     pub fn with_hide_callback<'cb, C>(
159         self,
160         callback: &'cb C,
161     ) -> Result<RevwalkWithHideCb<'repo, 'cb, C>, Error>
162     where
163         C: FnMut(Oid) -> bool,
164     {
165         let r = RevwalkWithHideCb {
166             revwalk: self,
167             _marker: marker::PhantomData,
168         };
169         unsafe {
170             raw::git_revwalk_add_hide_cb(
171                 r.revwalk.raw(),
172                 Some(revwalk_hide_cb::<C>),
173                 callback as *const _ as *mut c_void,
174             );
175         };
176         Ok(r)
177     }
178 
179     /// Hide the repository's HEAD
180     ///
181     /// For more information, see `hide`.
hide_head(&mut self) -> Result<(), Error>182     pub fn hide_head(&mut self) -> Result<(), Error> {
183         unsafe {
184             try_call!(raw::git_revwalk_hide_head(self.raw()));
185         }
186         Ok(())
187     }
188 
189     /// Hide matching references.
190     ///
191     /// The OIDs pointed to by the references that match the given glob pattern
192     /// and their ancestors will be hidden from the output on the revision walk.
193     ///
194     /// A leading 'refs/' is implied if not present as well as a trailing `/ \
195     /// *` if the glob lacks '?', ' \ *' or '['.
196     ///
197     /// Any references matching this glob which do not point to a committish
198     /// will be ignored.
hide_glob(&mut self, glob: &str) -> Result<(), Error>199     pub fn hide_glob(&mut self, glob: &str) -> Result<(), Error> {
200         let glob = CString::new(glob)?;
201         unsafe {
202             try_call!(raw::git_revwalk_hide_glob(self.raw, glob));
203         }
204         Ok(())
205     }
206 
207     /// Hide the OID pointed to by a reference.
208     ///
209     /// The reference must point to a committish.
hide_ref(&mut self, reference: &str) -> Result<(), Error>210     pub fn hide_ref(&mut self, reference: &str) -> Result<(), Error> {
211         let reference = CString::new(reference)?;
212         unsafe {
213             try_call!(raw::git_revwalk_hide_ref(self.raw, reference));
214         }
215         Ok(())
216     }
217 }
218 
219 impl<'repo> Binding for Revwalk<'repo> {
220     type Raw = *mut raw::git_revwalk;
from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo>221     unsafe fn from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo> {
222         Revwalk {
223             raw,
224             _marker: marker::PhantomData,
225         }
226     }
raw(&self) -> *mut raw::git_revwalk227     fn raw(&self) -> *mut raw::git_revwalk {
228         self.raw
229     }
230 }
231 
232 impl<'repo> Drop for Revwalk<'repo> {
drop(&mut self)233     fn drop(&mut self) {
234         unsafe { raw::git_revwalk_free(self.raw) }
235     }
236 }
237 
238 impl<'repo> Iterator for Revwalk<'repo> {
239     type Item = Result<Oid, Error>;
next(&mut self) -> Option<Result<Oid, Error>>240     fn next(&mut self) -> Option<Result<Oid, Error>> {
241         let mut out: raw::git_oid = raw::git_oid {
242             id: [0; raw::GIT_OID_RAWSZ],
243         };
244         unsafe {
245             try_call_iter!(raw::git_revwalk_next(&mut out, self.raw()));
246             Some(Ok(Binding::from_raw(&out as *const _)))
247         }
248     }
249 }
250 
251 impl<'repo, 'cb, C: FnMut(Oid) -> bool> Iterator for RevwalkWithHideCb<'repo, 'cb, C> {
252     type Item = Result<Oid, Error>;
next(&mut self) -> Option<Result<Oid, Error>>253     fn next(&mut self) -> Option<Result<Oid, Error>> {
254         let out = self.revwalk.next();
255         crate::panic::check();
256         out
257     }
258 }
259 
260 #[cfg(test)]
261 mod tests {
262     #[test]
smoke()263     fn smoke() {
264         let (_td, repo) = crate::test::repo_init();
265         let head = repo.head().unwrap();
266         let target = head.target().unwrap();
267 
268         let mut walk = repo.revwalk().unwrap();
269         walk.push(target).unwrap();
270 
271         let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
272 
273         assert_eq!(oids.len(), 1);
274         assert_eq!(oids[0], target);
275 
276         walk.reset().unwrap();
277         walk.push_head().unwrap();
278         assert_eq!(walk.by_ref().count(), 1);
279 
280         walk.reset().unwrap();
281         walk.push_head().unwrap();
282         walk.hide_head().unwrap();
283         assert_eq!(walk.by_ref().count(), 0);
284     }
285 
286     #[test]
smoke_hide_cb()287     fn smoke_hide_cb() {
288         let (_td, repo) = crate::test::repo_init();
289         let head = repo.head().unwrap();
290         let target = head.target().unwrap();
291 
292         let mut walk = repo.revwalk().unwrap();
293         walk.push(target).unwrap();
294 
295         let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
296 
297         assert_eq!(oids.len(), 1);
298         assert_eq!(oids[0], target);
299 
300         walk.reset().unwrap();
301         walk.push_head().unwrap();
302         assert_eq!(walk.by_ref().count(), 1);
303 
304         walk.reset().unwrap();
305         walk.push_head().unwrap();
306 
307         let hide_cb = |oid| oid == target;
308         let mut walk = walk.with_hide_callback(&hide_cb).unwrap();
309 
310         assert_eq!(walk.by_ref().count(), 0);
311 
312         let mut walk = walk.into_inner().unwrap();
313         walk.push_head().unwrap();
314         assert_eq!(walk.by_ref().count(), 1);
315     }
316 }
317