use smallvec::SmallVec; use super::LazyCommit; use crate::graph::{Commit, CommitterTimestamp, Either, Generation}; impl<'graph> LazyCommit<'graph> { /// Return an iterator over the parents of this commit. pub fn iter_parents(&self) -> Parents<'graph> { let backing = match &self.backing { Either::Left(buf) => Either::Left(gix_object::CommitRefIter::from_bytes(buf)), Either::Right((cache, pos)) => Either::Right((*cache, cache.commit_at(*pos).iter_parents())), }; Parents { backing } } /// Returns the timestamp at which this commit was created. /// /// This is the single-most important date for determining recency of commits. /// Note that this can only fail if the commit is backed by the object database *and* parsing fails. pub fn committer_timestamp(&self) -> Result { Ok(match &self.backing { Either::Left(buf) => { gix_object::CommitRefIter::from_bytes(buf) .committer()? .time .seconds_since_unix_epoch as CommitterTimestamp } Either::Right((cache, pos)) => cache.commit_at(*pos).committer_timestamp(), }) } /// Returns the generation of the commit if it is backed by a commit graph. pub fn generation(&self) -> Option { match &self.backing { Either::Left(_) => None, Either::Right((cache, pos)) => cache.commit_at(*pos).generation().into(), } } /// Convert ourselves into an owned version, which effectively detaches us from the underlying graph. /// Use `new_data()` to provide the `data` field for the owned `Commit`. pub fn to_owned(&self, new_data: impl FnOnce() -> T) -> Result, to_owned::Error> { let data = new_data(); Ok(match &self.backing { Either::Left(buf) => { use gix_object::commit::ref_iter::Token; let iter = gix_object::CommitRefIter::from_bytes(buf); let mut parents = SmallVec::default(); let mut timestamp = None; for token in iter { match token? { Token::Tree { .. } => {} Token::Parent { id } => parents.push(id), Token::Author { .. } => {} Token::Committer { signature } => { timestamp = Some(signature.time.seconds_since_unix_epoch as CommitterTimestamp); break; } _ => { unreachable!( "we break naturally after seeing the committer which is always at the same spot" ) } } } Commit { parents, commit_time: timestamp.unwrap_or_default(), generation: None, data, } } Either::Right((cache, pos)) => { let mut parents = SmallVec::default(); let commit = cache.commit_at(*pos); for pos in commit.iter_parents() { let pos = pos?; parents.push(cache.commit_at(pos).id().to_owned()); } Commit { parents, commit_time: commit.committer_timestamp(), generation: Some(commit.generation()), data, } } }) } } /// An iterator over the parents of a commit. pub struct Parents<'graph> { backing: Either< gix_object::CommitRefIter<'graph>, ( &'graph gix_commitgraph::Graph, gix_commitgraph::file::commit::Parents<'graph>, ), >, } impl<'graph> Iterator for Parents<'graph> { type Item = Result; fn next(&mut self) -> Option { match &mut self.backing { Either::Left(it) => { for token in it { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => return Some(Ok(id)), Ok(_unused_token) => break, Err(err) => return Some(Err(err.into())), } } None } Either::Right((cache, it)) => it .next() .map(|r| r.map(|pos| cache.id_at(pos).to_owned()).map_err(Into::into)), } } } /// pub mod iter_parents { /// The error returned by the [`Parents`][super::Parents] iterator. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("An error occurred when parsing commit parents")] DecodeCommit(#[from] gix_object::decode::Error), #[error("An error occurred when parsing parents from the commit graph")] DecodeCommitGraph(#[from] gix_commitgraph::file::commit::Error), } } /// pub mod to_owned { /// The error returned by [`to_owned()`][crate::graph::LazyCommit::to_owned()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("A commit could not be decoded during traversal")] Decode(#[from] gix_object::decode::Error), #[error("Could not find commit position in graph when traversing parents")] CommitGraphParent(#[from] gix_commitgraph::file::commit::Error), } }