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