use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashSet};
use std::fmt::Display;
use std::sync::Arc;
use super::reader::ObjectView;
use super::{Error, Repo};
use crate::{Commit, HistoryError, HistoryLimits, ObjectId, ObjectKind};
#[derive(Debug)]
pub struct Log {
reader: Arc<ObjectView>,
frontier: BinaryHeap<Candidate>,
seen: HashSet<ObjectId>,
pending: Vec<ObjectId>,
parents: usize,
failed: bool,
}
#[derive(Debug)]
struct Candidate {
seconds: i64,
id: ObjectId,
commit: Commit,
}
impl PartialEq for Candidate {
fn eq(&self, other: &Self) -> bool {
(self.seconds, self.id) == (other.seconds, other.id)
}
}
impl Eq for Candidate {}
impl PartialOrd for Candidate {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Candidate {
fn cmp(&self, other: &Self) -> Ordering {
(self.seconds, self.id).cmp(&(other.seconds, other.id))
}
}
impl Repo {
pub fn log(&self, revision: impl Display) -> crate::Result<Log> {
let root = self.reader.resolve(revision, Some(ObjectKind::Commit))?;
let mut log = Log {
reader: Arc::clone(&self.reader),
frontier: BinaryHeap::new(),
seen: HashSet::new(),
pending: Vec::new(),
parents: 0,
failed: false,
};
log.discover(root)?;
Ok(log)
}
}
impl Log {
fn discover(&mut self, id: ObjectId) -> crate::Result<()> {
if self.seen.contains(&id) {
return Ok(());
}
if self.seen.len() >= HistoryLimits::default().max_commits {
return Err(HistoryError::Limit("commits").into());
}
let commit = self.reader.read_commit(id)?;
let seconds = commit
.committer()?
.ok_or(Error::MissingCommitter { id })?
.date()?
.seconds;
self.seen.insert(id);
self.frontier.push(Candidate {
seconds,
id,
commit,
});
Ok(())
}
fn advance(&mut self) -> crate::Result<Option<(ObjectId, Commit)>> {
for id in std::mem::take(&mut self.pending) {
self.discover(id)?;
}
let Some(candidate) = self.frontier.pop() else {
return Ok(None);
};
if !self
.reader
.repository
.shallow_roots()
.contains(candidate.id)
{
self.parents = self
.parents
.checked_add(candidate.commit.parents().len())
.ok_or(HistoryError::Limit("parents"))?;
if self.parents > HistoryLimits::default().max_parents {
return Err(HistoryError::Limit("parents").into());
}
self.pending = candidate.commit.parents().to_vec();
}
Ok(Some((candidate.id, candidate.commit)))
}
}
impl Iterator for Log {
type Item = crate::Result<(ObjectId, Commit)>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
match self.advance() {
Ok(item) => item.map(Ok),
Err(error) => {
self.failed = true;
Some(Err(error))
}
}
}
}
impl std::iter::FusedIterator for Log {}