use std::marker::PhantomData;
use serde::Deserialize;
use crate::cob::{Op, TypeName};
use crate::git;
use crate::git::fmt::refspec::PatternString;
use crate::git::Oid;
use super::error;
use super::CobRange;
#[derive(Clone, Debug)]
pub(super) struct Walk {
from: Oid,
until: Until,
}
#[derive(Clone, Debug)]
pub enum Until {
Tip(Oid),
Glob(PatternString),
}
impl From<Oid> for Until {
fn from(tip: Oid) -> Self {
Self::Tip(tip)
}
}
impl From<PatternString> for Until {
fn from(glob: PatternString) -> Self {
Self::Glob(glob)
}
}
pub(super) struct WalkIter<'a> {
repo: &'a git::raw::Repository,
from: Option<Oid>,
inner: git::raw::Revwalk<'a>,
}
impl From<CobRange> for Walk {
fn from(history: CobRange) -> Self {
Self::new(history.root, history.until)
}
}
impl Walk {
pub(super) fn new(from: Oid, until: Until) -> Self {
Self { from, until }
}
pub(super) fn since(mut self, from: Oid) -> Self {
self.from = from;
self
}
pub(super) fn until(mut self, until: impl Into<Until>) -> Self {
self.until = until.into();
self
}
pub(super) fn iter(self, repo: &git::raw::Repository) -> Result<WalkIter<'_>, git::raw::Error> {
let mut walk = repo.revwalk()?;
walk.set_sorting(git::raw::Sort::TOPOLOGICAL.union(git::raw::Sort::REVERSE))?;
match self.until {
Until::Tip(tip) => walk.push_range(&format!("{}..{}", self.from, tip))?,
Until::Glob(glob) => {
walk.push(self.from.into())?;
walk.push_glob(glob.as_str())?
}
}
Ok(WalkIter {
repo,
from: Some(self.from),
inner: walk,
})
}
}
impl<'a> Iterator for WalkIter<'a> {
type Item = Result<git::raw::Commit<'a>, git::raw::Error>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(from) = self.from.take() {
return Some(self.repo.find_commit(from.into()));
}
let oid = self.inner.next()?;
Some(oid.and_then(|oid| self.repo.find_commit(oid)))
}
}
pub struct OpsIter<'a, A> {
walk: WalkIter<'a>,
typename: TypeName,
action: PhantomData<A>,
}
impl<A> Iterator for OpsIter<'_, A>
where
A: for<'de> Deserialize<'de>,
{
type Item = Result<Op<A>, error::Ops>;
fn next(&mut self) -> Option<Self::Item> {
let commit = self.walk.next()?;
match commit {
Ok(commit) => {
let entry = crate::git::Oid::from(commit.id());
self.walk.inner.hide(commit.id()).ok();
match self.load(entry) {
Ok(entry) => entry.map(Ok).or_else(|| self.next()),
Err(err) => match err {
error::Ops::Manifest { source: _ } => self.next(),
err => Some(Err(err)),
},
}
}
Err(err) => Some(Err(error::Ops::Commit { source: err })),
}
}
}
impl<'a, A> OpsIter<'a, A> {
pub(super) fn new(walk: WalkIter<'a>, typename: TypeName) -> Self {
Self {
walk,
typename,
action: PhantomData,
}
}
fn load(&self, entry: crate::git::Oid) -> Result<Option<Op<A>>, error::Ops>
where
A: for<'de> Deserialize<'de>,
{
let manifest = Op::<A>::manifest_of(self.walk.repo, &entry)
.map_err(|err| error::Ops::Manifest { source: err })?;
if manifest.type_name == self.typename {
let op =
Op::load(self.walk.repo, entry).map_err(|err| error::Ops::Load { source: err })?;
Ok(Some(op))
} else {
Ok(None)
}
}
}