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