mirror of
https://git.proxmox.com/git/rustc
synced 2026-01-17 15:16:42 +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 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);
|
|
}
|
|
}
|