use std::{
cmp::Ordering,
collections::{BinaryHeap, HashSet},
};
use crate::{GitError, ObjectId, Repository, Result};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Pending {
time: i64,
sequence: u64,
id: ObjectId,
parents: Vec<ObjectId>,
}
impl Ord for Pending {
fn cmp(&self, other: &Self) -> Ordering {
(self.time, self.sequence, self.id).cmp(&(other.time, other.sequence, other.id))
}
}
impl PartialOrd for Pending {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
pub struct RevWalk<'repo> {
repository: &'repo Repository,
pending: BinaryHeap<Pending>,
scheduled: HashSet<ObjectId>,
hidden: HashSet<ObjectId>,
first_parent: bool,
sequence: u64,
emitted: usize,
failed: bool,
}
impl<'repo> RevWalk<'repo> {
pub(crate) fn new(repository: &'repo Repository) -> Self {
Self {
repository,
pending: BinaryHeap::new(),
scheduled: HashSet::new(),
hidden: HashSet::new(),
first_parent: false,
sequence: 0,
emitted: 0,
failed: false,
}
}
pub fn push(&mut self, id: ObjectId) -> Result<&mut Self> {
self.schedule(id)?;
Ok(self)
}
pub fn push_head(&mut self) -> Result<&mut Self> {
let id = self
.repository
.head()?
.target
.ok_or_else(|| crate::GitError::NotFound("unborn HEAD".to_owned()))?;
self.push(id)
}
pub fn push_ref(&mut self, name: &str) -> Result<&mut Self> {
self.push(self.repository.resolve(name)?)
}
pub fn hide(&mut self, id: ObjectId) -> Result<&mut Self> {
let mut stack = vec![id];
while let Some(id) = stack.pop() {
if !self.hidden.insert(id) {
continue;
}
if self.hidden.len() > self.repository.limits().max_history_commits {
return Err(GitError::LimitExceeded {
resource: "revwalk hidden commits",
limit: self.repository.limits().max_history_commits,
});
}
stack.extend(self.repository.commit_metadata(id)?.parents);
}
Ok(self)
}
pub fn hide_ref(&mut self, name: &str) -> Result<&mut Self> {
self.hide(self.repository.resolve(name)?)
}
pub const fn first_parent(&mut self, enabled: bool) -> &mut Self {
self.first_parent = enabled;
self
}
pub fn reset(&mut self) {
self.pending.clear();
self.scheduled.clear();
self.hidden.clear();
self.sequence = 0;
self.emitted = 0;
self.failed = false;
}
fn schedule(&mut self, id: ObjectId) -> Result<()> {
if !self.scheduled.insert(id) {
return Ok(());
}
if self.scheduled.len() > self.repository.limits().max_history_commits {
return Err(GitError::LimitExceeded {
resource: "revwalk commits",
limit: self.repository.limits().max_history_commits,
});
}
let commit = self.repository.commit_metadata(id)?;
self.pending.push(Pending {
time: commit.committer_time,
sequence: self.sequence,
id,
parents: commit.parents,
});
self.sequence = self.sequence.wrapping_add(1);
Ok(())
}
}
impl Iterator for RevWalk<'_> {
type Item = Result<ObjectId>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed || self.emitted >= self.repository.limits().max_history_commits {
return None;
}
while let Some(item) = self.pending.pop() {
if self.hidden.contains(&item.id) {
continue;
}
let parents = if self.first_parent {
item.parents.get(..1).unwrap_or(&[])
} else {
&item.parents
};
for parent in parents {
if let Err(error) = self.schedule(*parent) {
self.failed = true;
return Some(Err(error));
}
}
self.emitted += 1;
return Some(Ok(item.id));
}
None
}
}
impl Repository {
#[must_use]
pub fn revwalk(&self) -> RevWalk<'_> {
RevWalk::new(self)
}
}