diff options
Diffstat (limited to 'vendor/git2/src/revwalk.rs')
-rw-r--r-- | vendor/git2/src/revwalk.rs | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/vendor/git2/src/revwalk.rs b/vendor/git2/src/revwalk.rs new file mode 100644 index 0000000..7837f00 --- /dev/null +++ b/vendor/git2/src/revwalk.rs @@ -0,0 +1,316 @@ +use libc::{c_int, c_uint, c_void}; +use std::ffi::CString; +use std::marker; + +use crate::util::Binding; +use crate::{panic, raw, Error, Oid, Repository, Sort}; + +/// A revwalk allows traversal of the commit graph defined by including one or +/// more leaves and excluding one or more roots. +pub struct Revwalk<'repo> { + raw: *mut raw::git_revwalk, + _marker: marker::PhantomData<&'repo Repository>, +} + +/// A `Revwalk` with an associated "hide callback", see `with_hide_callback` +pub struct RevwalkWithHideCb<'repo, 'cb, C> +where + C: FnMut(Oid) -> bool, +{ + revwalk: Revwalk<'repo>, + _marker: marker::PhantomData<&'cb C>, +} + +extern "C" fn revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int +where + C: FnMut(Oid) -> bool, +{ + panic::wrap(|| unsafe { + let hide_cb = payload as *mut C; + if (*hide_cb)(Oid::from_raw(commit_id)) { + 1 + } else { + 0 + } + }) + .unwrap_or(-1) +} + +impl<'repo, 'cb, C: FnMut(Oid) -> bool> RevwalkWithHideCb<'repo, 'cb, C> { + /// Consumes the `RevwalkWithHideCb` and returns the contained `Revwalk`. + /// + /// Note that this will reset the `Revwalk`. + pub fn into_inner(mut self) -> Result<Revwalk<'repo>, Error> { + self.revwalk.reset()?; + Ok(self.revwalk) + } +} + +impl<'repo> Revwalk<'repo> { + /// Reset a revwalk to allow re-configuring it. + /// + /// The revwalk is automatically reset when iteration of its commits + /// completes. + pub fn reset(&mut self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_reset(self.raw())); + } + Ok(()) + } + + /// Set the order in which commits are visited. + pub fn set_sorting(&mut self, sort_mode: Sort) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_sorting( + self.raw(), + sort_mode.bits() as c_uint + )); + } + Ok(()) + } + + /// Simplify the history by first-parent + /// + /// No parents other than the first for each commit will be enqueued. + pub fn simplify_first_parent(&mut self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_simplify_first_parent(self.raw)); + } + Ok(()) + } + + /// Mark a commit to start traversal from. + /// + /// The given OID must belong to a commitish on the walked repository. + /// + /// The given commit will be used as one of the roots when starting the + /// revision walk. At least one commit must be pushed onto the walker before + /// a walk can be started. + pub fn push(&mut self, oid: Oid) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_push(self.raw(), oid.raw())); + } + Ok(()) + } + + /// Push the repository's HEAD + /// + /// For more information, see `push`. + pub fn push_head(&mut self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_push_head(self.raw())); + } + Ok(()) + } + + /// Push matching references + /// + /// The OIDs pointed to by the references that match the given glob pattern + /// will be pushed to the revision walker. + /// + /// A leading 'refs/' is implied if not present as well as a trailing `/ \ + /// *` if the glob lacks '?', ' \ *' or '['. + /// + /// Any references matching this glob which do not point to a commitish + /// will be ignored. + pub fn push_glob(&mut self, glob: &str) -> Result<(), Error> { + let glob = CString::new(glob)?; + unsafe { + try_call!(raw::git_revwalk_push_glob(self.raw, glob)); + } + Ok(()) + } + + /// Push and hide the respective endpoints of the given range. + /// + /// The range should be of the form `<commit>..<commit>` where each + /// `<commit>` is in the form accepted by `revparse_single`. The left-hand + /// commit will be hidden and the right-hand commit pushed. + pub fn push_range(&mut self, range: &str) -> Result<(), Error> { + let range = CString::new(range)?; + unsafe { + try_call!(raw::git_revwalk_push_range(self.raw, range)); + } + Ok(()) + } + + /// Push the OID pointed to by a reference + /// + /// The reference must point to a commitish. + pub fn push_ref(&mut self, reference: &str) -> Result<(), Error> { + let reference = CString::new(reference)?; + unsafe { + try_call!(raw::git_revwalk_push_ref(self.raw, reference)); + } + Ok(()) + } + + /// Mark a commit as not of interest to this revwalk. + pub fn hide(&mut self, oid: Oid) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_hide(self.raw(), oid.raw())); + } + Ok(()) + } + + /// Hide all commits for which the callback returns true from + /// the walk. + pub fn with_hide_callback<'cb, C>( + self, + callback: &'cb mut C, + ) -> Result<RevwalkWithHideCb<'repo, 'cb, C>, Error> + where + C: FnMut(Oid) -> bool, + { + let r = RevwalkWithHideCb { + revwalk: self, + _marker: marker::PhantomData, + }; + unsafe { + raw::git_revwalk_add_hide_cb( + r.revwalk.raw(), + Some(revwalk_hide_cb::<C>), + callback as *mut _ as *mut c_void, + ); + }; + Ok(r) + } + + /// Hide the repository's HEAD + /// + /// For more information, see `hide`. + pub fn hide_head(&mut self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_revwalk_hide_head(self.raw())); + } + Ok(()) + } + + /// Hide matching references. + /// + /// The OIDs pointed to by the references that match the given glob pattern + /// and their ancestors will be hidden from the output on the revision walk. + /// + /// A leading 'refs/' is implied if not present as well as a trailing `/ \ + /// *` if the glob lacks '?', ' \ *' or '['. + /// + /// Any references matching this glob which do not point to a commitish + /// will be ignored. + pub fn hide_glob(&mut self, glob: &str) -> Result<(), Error> { + let glob = CString::new(glob)?; + unsafe { + try_call!(raw::git_revwalk_hide_glob(self.raw, glob)); + } + Ok(()) + } + + /// Hide the OID pointed to by a reference. + /// + /// The reference must point to a commitish. + pub fn hide_ref(&mut self, reference: &str) -> Result<(), Error> { + let reference = CString::new(reference)?; + unsafe { + try_call!(raw::git_revwalk_hide_ref(self.raw, reference)); + } + Ok(()) + } +} + +impl<'repo> Binding for Revwalk<'repo> { + type Raw = *mut raw::git_revwalk; + unsafe fn from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo> { + Revwalk { + raw, + _marker: marker::PhantomData, + } + } + fn raw(&self) -> *mut raw::git_revwalk { + self.raw + } +} + +impl<'repo> Drop for Revwalk<'repo> { + fn drop(&mut self) { + unsafe { raw::git_revwalk_free(self.raw) } + } +} + +impl<'repo> Iterator for Revwalk<'repo> { + type Item = Result<Oid, Error>; + fn next(&mut self) -> Option<Result<Oid, Error>> { + let mut out: raw::git_oid = raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }; + unsafe { + try_call_iter!(raw::git_revwalk_next(&mut out, self.raw())); + Some(Ok(Binding::from_raw(&out as *const _))) + } + } +} + +impl<'repo, 'cb, C: FnMut(Oid) -> bool> Iterator for RevwalkWithHideCb<'repo, 'cb, C> { + type Item = Result<Oid, Error>; + fn next(&mut self) -> Option<Result<Oid, Error>> { + let out = self.revwalk.next(); + crate::panic::check(); + out + } +} + +#[cfg(test)] +mod tests { + #[test] + fn smoke() { + let (_td, repo) = crate::test::repo_init(); + let head = repo.head().unwrap(); + let target = head.target().unwrap(); + + let mut walk = repo.revwalk().unwrap(); + walk.push(target).unwrap(); + + let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap(); + + assert_eq!(oids.len(), 1); + assert_eq!(oids[0], target); + + walk.reset().unwrap(); + walk.push_head().unwrap(); + assert_eq!(walk.by_ref().count(), 1); + + walk.reset().unwrap(); + walk.push_head().unwrap(); + walk.hide_head().unwrap(); + assert_eq!(walk.by_ref().count(), 0); + } + + #[test] + fn smoke_hide_cb() { + let (_td, repo) = crate::test::repo_init(); + let head = repo.head().unwrap(); + let target = head.target().unwrap(); + + let mut walk = repo.revwalk().unwrap(); + walk.push(target).unwrap(); + + let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap(); + + assert_eq!(oids.len(), 1); + assert_eq!(oids[0], target); + + walk.reset().unwrap(); + walk.push_head().unwrap(); + assert_eq!(walk.by_ref().count(), 1); + + walk.reset().unwrap(); + walk.push_head().unwrap(); + + let mut hide_cb = |oid| oid == target; + let mut walk = walk.with_hide_callback(&mut hide_cb).unwrap(); + + assert_eq!(walk.by_ref().count(), 0); + + let mut walk = walk.into_inner().unwrap(); + walk.push_head().unwrap(); + assert_eq!(walk.by_ref().count(), 1); + } +} |