use std::cmp::Ordering;
use std::cmp::min;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::rc::Rc;
use itertools::Itertools as _;
use super::bit_set::PositionsBitSet;
use super::composite::CompositeCommitIndex;
use super::composite::CompositeIndex;
use super::entry::CommitIndexEntry;
use super::entry::GlobalCommitPosition;
use super::rev_walk::RevWalk;
use super::revset_engine::BoxedRevWalk;
use crate::backend::CommitId;
use crate::graph::GraphEdge;
use crate::graph::GraphNode;
use crate::revset::RevsetEvaluationError;
type CommitGraphEdge = GraphEdge<GlobalCommitPosition>;
pub(super) struct RevsetGraphWalk<'a> {
input_set_walk: BoxedRevWalk<'a>,
look_ahead: VecDeque<GlobalCommitPosition>,
min_position: GlobalCommitPosition,
edges: BTreeMap<GlobalCommitPosition, Rc<[CommitGraphEdge]>>,
skip_transitive_edges: bool,
}
impl<'a> RevsetGraphWalk<'a> {
pub fn new(input_set_walk: BoxedRevWalk<'a>, skip_transitive_edges: bool) -> Self {
Self {
input_set_walk,
look_ahead: VecDeque::new(),
min_position: GlobalCommitPosition::MAX,
edges: Default::default(),
skip_transitive_edges,
}
}
fn next_index_position(
&mut self,
index: &CompositeIndex,
) -> Result<Option<GlobalCommitPosition>, RevsetEvaluationError> {
match self.look_ahead.pop_back() {
Some(position) => Ok(Some(position)),
None => self.input_set_walk.next(index).transpose(),
}
}
fn pop_edges_from_internal_commit(
&mut self,
index: &CompositeIndex,
index_entry: &CommitIndexEntry,
) -> Result<Rc<[CommitGraphEdge]>, RevsetEvaluationError> {
let position = index_entry.position();
while let Some(entry) = self.edges.last_entry() {
match entry.key().cmp(&position) {
Ordering::Less => break, Ordering::Equal => return Ok(entry.remove()),
Ordering::Greater => entry.remove(),
};
}
self.new_edges_from_internal_commit(index, index_entry)
}
fn new_edges_from_internal_commit(
&mut self,
index: &CompositeIndex,
index_entry: &CommitIndexEntry,
) -> Result<Rc<[CommitGraphEdge]>, RevsetEvaluationError> {
let mut parent_entries = index_entry.parents();
if parent_entries.len() == 1 {
let parent = parent_entries.next().unwrap();
let parent_position = parent.position();
self.consume_to(index, parent_position)?;
if self.look_ahead.binary_search(&parent_position).is_ok() {
Ok([CommitGraphEdge::direct(parent_position)].into())
} else {
let parent_edges = self.edges_from_external_commit(index, parent)?;
if parent_edges.iter().all(|edge| edge.is_missing()) {
Ok([CommitGraphEdge::missing(parent_position)].into())
} else {
Ok(parent_edges.clone())
}
}
} else {
let mut edges = Vec::new();
let mut known_ancestors = PositionsBitSet::with_max_pos(index_entry.position());
for parent in parent_entries {
let parent_position = parent.position();
self.consume_to(index, parent_position)?;
if self.look_ahead.binary_search(&parent_position).is_ok() {
edges.push(CommitGraphEdge::direct(parent_position));
} else {
let parent_edges = self.edges_from_external_commit(index, parent)?;
if parent_edges.iter().all(|edge| edge.is_missing()) {
edges.push(CommitGraphEdge::missing(parent_position));
} else {
edges.extend(
parent_edges
.iter()
.filter(|edge| !known_ancestors.get_set(edge.target)),
);
}
}
}
if self.skip_transitive_edges {
self.remove_transitive_edges(index.commits(), &mut edges);
}
Ok(edges.into())
}
}
fn edges_from_external_commit(
&mut self,
index: &CompositeIndex,
index_entry: CommitIndexEntry<'_>,
) -> Result<&Rc<[CommitGraphEdge]>, RevsetEvaluationError> {
let position = index_entry.position();
let mut stack = vec![index_entry];
while let Some(entry) = stack.last() {
let position = entry.position();
if self.edges.contains_key(&position) {
stack.pop().unwrap();
continue;
}
let mut parent_entries = entry.parents();
let complete_parent_edges = if parent_entries.len() == 1 {
let parent = parent_entries.next().unwrap();
let parent_position = parent.position();
self.consume_to(index, parent_position)?;
if self.look_ahead.binary_search(&parent_position).is_ok() {
Some([CommitGraphEdge::indirect(parent_position)].into())
} else if let Some(parent_edges) = self.edges.get(&parent_position) {
if parent_edges.iter().all(|edge| edge.is_missing()) {
Some([CommitGraphEdge::missing(parent_position)].into())
} else {
Some(parent_edges.clone())
}
} else if parent_position < self.min_position {
Some([CommitGraphEdge::missing(parent_position)].into())
} else {
stack.push(parent);
None
}
} else {
let mut edges = Vec::new();
let mut known_ancestors = PositionsBitSet::with_max_pos(position);
let mut parents_complete = true;
for parent in parent_entries {
let parent_position = parent.position();
self.consume_to(index, parent_position)?;
if self.look_ahead.binary_search(&parent_position).is_ok() {
edges.push(CommitGraphEdge::indirect(parent_position));
} else if let Some(parent_edges) = self.edges.get(&parent_position) {
if parent_edges.iter().all(|edge| edge.is_missing()) {
edges.push(CommitGraphEdge::missing(parent_position));
} else {
edges.extend(
parent_edges
.iter()
.filter(|edge| !known_ancestors.get_set(edge.target)),
);
}
} else if parent_position < self.min_position {
edges.push(CommitGraphEdge::missing(parent_position));
} else {
stack.push(parent);
parents_complete = false;
}
}
parents_complete.then(|| {
if self.skip_transitive_edges {
self.remove_transitive_edges(index.commits(), &mut edges);
}
edges.into()
})
};
if let Some(edges) = complete_parent_edges {
stack.pop().unwrap();
self.edges.insert(position, edges);
}
}
Ok(self.edges.get(&position).unwrap())
}
fn remove_transitive_edges(
&self,
index: &CompositeCommitIndex,
edges: &mut Vec<CommitGraphEdge>,
) {
if !edges.iter().any(|edge| edge.is_indirect()) {
return;
}
let Some((min_pos, max_pos)) = reachable_positions(edges).minmax().into_option() else {
return;
};
let enqueue_parents = |work: &mut Vec<GlobalCommitPosition>, entry: &CommitIndexEntry| {
if let Some(edges) = self.edges.get(&entry.position()) {
work.extend(reachable_positions(edges).filter(|&pos| pos >= min_pos));
} else {
let positions = entry.parent_positions();
work.extend(positions.into_iter().filter(|&pos| pos >= min_pos));
}
};
let mut min_generation = u32::MAX;
let mut initial_targets = PositionsBitSet::with_max_pos(max_pos);
let mut work = vec![];
for pos in reachable_positions(edges) {
initial_targets.set(pos);
let entry = index.entry_by_pos(pos);
min_generation = min(min_generation, entry.generation_number());
enqueue_parents(&mut work, &entry);
}
let mut unwanted = PositionsBitSet::with_max_pos(max_pos);
while let Some(pos) = work.pop() {
if unwanted.get_set(pos) {
continue;
}
if initial_targets.get(pos) {
continue;
}
let entry = index.entry_by_pos(pos);
if entry.generation_number() < min_generation {
continue;
}
enqueue_parents(&mut work, &entry);
}
edges.retain(|edge| edge.is_missing() || !unwanted.get(edge.target));
}
fn consume_to(
&mut self,
index: &CompositeIndex,
pos: GlobalCommitPosition,
) -> Result<(), RevsetEvaluationError> {
while pos < self.min_position {
if let Some(next_position) = self.input_set_walk.next(index).transpose()? {
self.look_ahead.push_front(next_position);
self.min_position = next_position;
} else {
break;
}
}
Ok(())
}
fn try_next(
&mut self,
index: &CompositeIndex,
) -> Result<Option<GraphNode<CommitId>>, RevsetEvaluationError> {
let Some(position) = self.next_index_position(index)? else {
return Ok(None);
};
let entry = index.commits().entry_by_pos(position);
let edges = self.pop_edges_from_internal_commit(index, &entry)?;
let edges = edges
.iter()
.map(|edge| edge.map(|pos| index.commits().entry_by_pos(pos).commit_id()))
.collect();
Ok(Some((entry.commit_id(), edges)))
}
}
impl RevWalk<CompositeIndex> for RevsetGraphWalk<'_> {
type Item = Result<GraphNode<CommitId>, RevsetEvaluationError>;
fn next(&mut self, index: &CompositeIndex) -> Option<Self::Item> {
self.try_next(index).transpose()
}
}
fn reachable_positions(
edges: &[CommitGraphEdge],
) -> impl DoubleEndedIterator<Item = GlobalCommitPosition> {
edges
.iter()
.filter(|edge| !edge.is_missing())
.map(|edge| edge.target)
}