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