use std::ops::Deref;
use knot_git::{GitError, Repo};
use knot_types::{Oid, UnixSeconds};
pub(super) const MAX_TAG_DEPTH: usize = 32;
pub(super) enum Peeled {
Commit(gix::ObjectId),
Direct(gix::ObjectId),
}
pub(super) struct History<'a> {
repo: &'a Repo,
graph: Option<gix::commitgraph::Graph>,
}
impl<'a> History<'a> {
pub(super) fn new(repo: &'a Repo) -> Self {
Self {
repo,
graph: repo.git().commit_graph().ok(),
}
}
pub(super) fn commit_tree_and_parents(
&self,
commit: Oid,
) -> Result<(gix::ObjectId, Vec<gix::ObjectId>), GitError> {
if let Some(graph) = &self.graph
&& let Some(node) = graph.commit_by_id(commit.object_id())
{
let parents = node
.iter_parents()
.map(|position| position.map(|at| graph.commit_at(at).id().to_owned()))
.collect::<Result<Vec<_>, _>>();
if let Ok(parents) = parents {
return Ok((node.root_tree_id().to_owned(), parents));
}
}
let object = self.commit(commit)?;
let tree = object
.tree_id()
.map_err(|error| GitError::Decode(error.to_string()))?
.detach();
Ok((tree, object.parent_ids().map(|id| id.detach()).collect()))
}
pub(super) fn commit_time(&self, id: gix::ObjectId) -> Result<UnixSeconds, GitError> {
if let Some(node) = self.graph.as_ref().and_then(|graph| graph.commit_by_id(id)) {
return Ok(UnixSeconds::new(node.committer_timestamp() as i64));
}
let object = self.commit(Oid::from(id))?;
let time = object
.committer()
.map_err(|error| GitError::Decode(error.to_string()))?
.time()
.map_err(|error| GitError::Decode(error.to_string()))?;
Ok(UnixSeconds::new(time.seconds))
}
fn commit(&self, oid: Oid) -> Result<gix::Commit<'_>, GitError> {
self.git()
.find_object(oid.object_id())
.map_err(|error| GitError::Corrupt {
oid,
message: error.to_string(),
})?
.try_into_commit()
.map_err(|_| GitError::ObjectType {
oid,
expected: "commit",
})
}
pub(super) fn peel(
&self,
mut oid: gix::ObjectId,
tags: &mut Vec<Oid>,
depth: usize,
) -> Result<Peeled, GitError> {
for _ in 0..depth {
let object = self
.git()
.find_object(oid)
.map_err(|error| GitError::Corrupt {
oid: Oid::from(oid),
message: error.to_string(),
})?;
match object.kind {
gix::object::Kind::Commit => return Ok(Peeled::Commit(oid)),
gix::object::Kind::Tree | gix::object::Kind::Blob => {
return Ok(Peeled::Direct(oid));
}
gix::object::Kind::Tag => {
tags.push(Oid::from(oid));
oid = object
.try_into_tag()
.map_err(|error| GitError::Decode(error.to_string()))?
.target_id()
.map_err(|error| GitError::Decode(error.to_string()))?
.detach();
}
}
}
Err(GitError::DepthExceeded("annotated tag chain"))
}
}
impl Deref for History<'_> {
type Target = Repo;
fn deref(&self) -> &Repo {
self.repo
}
}