git2/
revwalk.rs

1use libc::{c_int, c_uint, c_void};
2use std::ffi::CString;
3use std::marker;
4
5use crate::util::Binding;
6use 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.
10pub struct Revwalk<'repo> {
11    raw: *mut raw::git_revwalk,
12    _marker: marker::PhantomData<&'repo Repository>,
13}
14
15/// A `Revwalk` with an associated "hide callback", see `with_hide_callback`
16pub struct RevwalkWithHideCb<'repo, 'cb, C>
17where
18    C: FnMut(Oid) -> bool,
19{
20    revwalk: Revwalk<'repo>,
21    _marker: marker::PhantomData<&'cb C>,
22}
23
24extern "C" fn revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int
25where
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
39impl<'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`.
43    pub fn into_inner(mut self) -> Result<Revwalk<'repo>, Error> {
44        self.revwalk.reset()?;
45        Ok(self.revwalk)
46    }
47}
48
49impl<'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.
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.
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.
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 commitish 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.
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`.
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 commitish
115    /// will be ignored.
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.
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 commitish.
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.
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.
158    pub fn with_hide_callback<'cb, C>(
159        self,
160        callback: &'cb mut 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 *mut _ as *mut c_void,
174            );
175        };
176        Ok(r)
177    }
178
179    /// Hide the repository's HEAD
180    ///
181    /// For more information, see `hide`.
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 commitish
198    /// will be ignored.
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 commitish.
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
219impl<'repo> Binding for Revwalk<'repo> {
220    type Raw = *mut raw::git_revwalk;
221    unsafe fn from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo> {
222        Revwalk {
223            raw,
224            _marker: marker::PhantomData,
225        }
226    }
227    fn raw(&self) -> *mut raw::git_revwalk {
228        self.raw
229    }
230}
231
232impl<'repo> Drop for Revwalk<'repo> {
233    fn drop(&mut self) {
234        unsafe { raw::git_revwalk_free(self.raw) }
235    }
236}
237
238impl<'repo> Iterator for Revwalk<'repo> {
239    type Item = 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
251impl<'repo, 'cb, C: FnMut(Oid) -> bool> Iterator for RevwalkWithHideCb<'repo, 'cb, C> {
252    type Item = 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)]
261mod tests {
262    #[test]
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]
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 mut hide_cb = |oid| oid == target;
308        let mut walk = walk.with_hide_callback(&mut 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}