use gix_hash::ObjectId;
use gix_object::FindExt;
use gix_traverse::commit::simple::CommitTimeOrder;
use crate::{ext::ObjectIdExt, revision, Repository};
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
SimpleTraversal(#[from] gix_traverse::commit::simple::Error),
#[error(transparent)]
ShallowCommits(#[from] crate::shallow::read::Error),
#[error(transparent)]
ConfigBoolean(#[from] crate::config::boolean::Error),
}
#[derive(Default, Debug, Copy, Clone)]
pub enum Sorting {
#[default]
BreadthFirst,
ByCommitTime(CommitTimeOrder),
ByCommitTimeCutoff {
order: CommitTimeOrder,
seconds: gix_date::SecondsSinceUnixEpoch,
},
}
impl Sorting {
fn into_simple(self) -> Option<gix_traverse::commit::simple::Sorting> {
Some(match self {
Sorting::BreadthFirst => gix_traverse::commit::simple::Sorting::BreadthFirst,
Sorting::ByCommitTime(order) => gix_traverse::commit::simple::Sorting::ByCommitTime(order),
Sorting::ByCommitTimeCutoff { seconds, order } => {
gix_traverse::commit::simple::Sorting::ByCommitTimeCutoff { order, seconds }
}
})
}
}
#[derive(Debug, Clone)]
pub struct Info<'repo> {
pub id: gix_hash::ObjectId,
pub parent_ids: gix_traverse::commit::ParentIds,
pub commit_time: Option<gix_date::SecondsSinceUnixEpoch>,
repo: &'repo Repository,
}
impl<'repo> Info<'repo> {
pub fn id(&self) -> crate::Id<'repo> {
self.id.attach(self.repo)
}
pub fn object(&self) -> Result<crate::Commit<'repo>, crate::object::find::existing::Error> {
Ok(self.id().object()?.into_commit())
}
pub fn parent_ids(&self) -> impl Iterator<Item = crate::Id<'repo>> + '_ {
self.parent_ids.iter().map(|id| id.attach(self.repo))
}
pub fn commit_time(&self) -> gix_date::SecondsSinceUnixEpoch {
self.commit_time.expect("traversal involving date caused it to be set")
}
}
impl<'repo> Info<'repo> {
pub fn new(info: gix_traverse::commit::Info, repo: &'repo Repository) -> Self {
Info {
id: info.id,
parent_ids: info.parent_ids,
commit_time: info.commit_time,
repo,
}
}
pub fn detach(self) -> gix_traverse::commit::Info {
gix_traverse::commit::Info {
id: self.id,
parent_ids: self.parent_ids,
commit_time: self.commit_time,
}
}
}
pub struct Platform<'repo> {
pub repo: &'repo Repository,
pub(crate) tips: Vec<ObjectId>,
pub(crate) hidden: Vec<ObjectId>,
pub(crate) boundary: Vec<ObjectId>,
pub(crate) sorting: Sorting,
pub(crate) parents: gix_traverse::commit::Parents,
pub(crate) use_commit_graph: Option<bool>,
pub(crate) commit_graph: Option<gix_commitgraph::Graph>,
}
impl<'repo> Platform<'repo> {
pub(crate) fn new(tips: impl IntoIterator<Item = impl Into<ObjectId>>, repo: &'repo Repository) -> Self {
revision::walk::Platform {
repo,
tips: tips.into_iter().map(Into::into).collect(),
hidden: Vec::new(),
sorting: Default::default(),
parents: Default::default(),
use_commit_graph: None,
commit_graph: None,
boundary: Vec::new(),
}
}
}
impl Platform<'_> {
pub fn sorting(mut self, sorting: Sorting) -> Self {
self.sorting = sorting;
self
}
pub fn first_parent_only(mut self) -> Self {
self.parents = gix_traverse::commit::Parents::First;
self
}
pub fn use_commit_graph(mut self, toggle: impl Into<Option<bool>>) -> Self {
self.use_commit_graph = toggle.into();
self
}
pub fn with_commit_graph(mut self, graph: Option<gix_commitgraph::Graph>) -> Self {
self.commit_graph = graph;
self
}
pub fn with_boundary(mut self, ids: impl IntoIterator<Item = impl Into<ObjectId>>) -> Self {
let (mut cutoff, order) = match self.sorting {
Sorting::ByCommitTimeCutoff { seconds, order } => (Some(seconds), order),
Sorting::ByCommitTime(order) => (None, order),
Sorting::BreadthFirst => (None, CommitTimeOrder::default()),
};
for id in ids.into_iter() {
let id = id.into();
if !self.boundary.contains(&id) {
if let Some(time) = self.repo.find_commit(id).ok().and_then(|c| c.time().ok()) {
if cutoff.is_none() || cutoff > Some(time.seconds) {
cutoff = time.seconds.into();
}
}
self.boundary.push(id);
}
}
if let Some(cutoff) = cutoff {
self.sorting = Sorting::ByCommitTimeCutoff { seconds: cutoff, order }
}
self
}
pub fn with_hidden(mut self, tips: impl IntoIterator<Item = impl Into<ObjectId>>) -> Self {
self.hidden = tips.into_iter().map(Into::into).collect();
self
}
}
impl<'repo> Platform<'repo> {
pub fn selected(
self,
mut filter: impl FnMut(&gix_hash::oid) -> bool + 'repo,
) -> Result<revision::Walk<'repo>, Error> {
let Platform {
repo,
tips,
sorting,
parents,
use_commit_graph,
commit_graph,
mut boundary,
hidden,
} = self;
boundary.sort();
Ok(revision::Walk {
repo,
inner: Box::new(
gix_traverse::commit::Simple::filtered(tips, &repo.objects, {
let shallow_commits = repo.shallow_commits()?;
let mut grafted_parents_to_skip = Vec::new();
let mut buf = Vec::new();
move |id| {
if !filter(id) {
return false;
}
let id = id.to_owned();
if boundary.binary_search(&id).is_ok() {
return false;
}
match shallow_commits.as_ref() {
Some(commits) => {
if let Ok(idx) = grafted_parents_to_skip.binary_search(&id) {
grafted_parents_to_skip.remove(idx);
return false;
}
if commits.binary_search(&id).is_ok() {
if let Ok(commit) = repo.objects.find_commit_iter(&id, &mut buf) {
grafted_parents_to_skip.extend(commit.parent_ids());
grafted_parents_to_skip.sort();
}
}
true
}
None => true,
}
}
})
.sorting(sorting.into_simple().expect("for now there is nothing else"))?
.parents(parents)
.hide(hidden)?
.commit_graph(
commit_graph.or(use_commit_graph
.map_or_else(|| self.repo.config.may_use_commit_graph(), Ok)?
.then(|| self.repo.commit_graph().ok())
.flatten()),
)
.map(|res| res.map_err(iter::Error::from)),
),
})
}
pub fn all(self) -> Result<revision::Walk<'repo>, Error> {
self.selected(|_| true)
}
}
pub mod iter {
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
SimpleTraversal(#[from] gix_traverse::commit::simple::Error),
}
}
pub(crate) mod iter_impl {
pub struct Walk<'repo> {
pub repo: &'repo crate::Repository,
pub(crate) inner: Box<dyn Iterator<Item = Result<gix_traverse::commit::Info, super::iter::Error>> + 'repo>,
}
impl<'repo> Iterator for Walk<'repo> {
type Item = Result<super::Info<'repo>, super::iter::Error>;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|res| res.map(|info| super::Info::new(info, self.repo)))
}
}
}