mirror of
				https://git.proxmox.com/git/rustc
				synced 2025-10-31 18:10:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			317 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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 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 *const _ 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 hide_cb = |oid| oid == target;
 | |
|         let mut walk = walk.with_hide_callback(&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);
 | |
|     }
 | |
| }
 | 
