use std::cmp::Reverse;
use std::cmp::max;
use std::collections::HashMap;
use std::collections::HashSet;
use std::iter::Fuse;
use std::iter::FusedIterator;
use std::ops::Range;
use smallvec::SmallVec;
use super::composite::CompositeCommitIndex;
use super::composite::CompositeIndex;
use super::entry::GlobalCommitPosition;
use super::entry::SmallGlobalCommitPositionsVec;
use super::rev_walk_queue::RevWalkQueue;
use super::rev_walk_queue::RevWalkWorkItem;
use crate::revset::PARENTS_RANGE_FULL;
pub(super) trait RevWalk<I: ?Sized> {
type Item;
fn next(&mut self, index: &I) -> Option<Self::Item>;
fn filter_map<B, F>(self, f: F) -> FilterMapRevWalk<Self, F>
where
Self: Sized,
F: FnMut(&I, Self::Item) -> Option<B>,
{
FilterMapRevWalk { walk: self, f }
}
fn map<B, F>(self, f: F) -> MapRevWalk<Self, F>
where
Self: Sized,
F: FnMut(&I, Self::Item) -> B,
{
MapRevWalk { walk: self, f }
}
fn peekable(self) -> PeekableRevWalk<I, Self>
where
Self: Sized,
{
PeekableRevWalk {
walk: self,
peeked: None,
}
}
fn attach(self, index: &I) -> RevWalkBorrowedIndexIter<'_, I, Self>
where
Self: Sized,
{
RevWalkBorrowedIndexIter { index, walk: self }
}
}
impl<I: ?Sized, W: RevWalk<I> + ?Sized> RevWalk<I> for Box<W> {
type Item = W::Item;
fn next(&mut self, index: &I) -> Option<Self::Item> {
<W as RevWalk<I>>::next(self, index)
}
}
#[derive(Clone, Debug)]
pub(super) struct EagerRevWalk<T> {
iter: Fuse<T>,
}
impl<T: Iterator> EagerRevWalk<T> {
pub fn new(iter: T) -> Self {
Self { iter: iter.fuse() }
}
}
impl<I: ?Sized, T: Iterator> RevWalk<I> for EagerRevWalk<T> {
type Item = T::Item;
fn next(&mut self, _index: &I) -> Option<Self::Item> {
self.iter.next()
}
}
#[derive(Clone, Debug)]
#[must_use]
pub(super) struct FilterMapRevWalk<W, F> {
walk: W,
f: F,
}
impl<B, I, W, F> RevWalk<I> for FilterMapRevWalk<W, F>
where
I: ?Sized,
W: RevWalk<I>,
F: FnMut(&I, W::Item) -> Option<B>,
{
type Item = B;
fn next(&mut self, index: &I) -> Option<Self::Item> {
while let Some(item) = self.walk.next(index) {
if let Some(new_item) = (self.f)(index, item) {
return Some(new_item);
}
}
None
}
}
#[derive(Clone, Debug)]
#[must_use]
pub(super) struct MapRevWalk<W, F> {
walk: W,
f: F,
}
impl<B, I, W, F> RevWalk<I> for MapRevWalk<W, F>
where
I: ?Sized,
W: RevWalk<I>,
F: FnMut(&I, W::Item) -> B,
{
type Item = B;
fn next(&mut self, index: &I) -> Option<Self::Item> {
self.walk.next(index).map(|item| (self.f)(index, item))
}
}
#[derive(Clone, Debug)]
#[must_use]
pub(super) struct PeekableRevWalk<I: ?Sized, W: RevWalk<I>> {
walk: W,
peeked: Option<W::Item>,
}
impl<I: ?Sized, W: RevWalk<I>> PeekableRevWalk<I, W> {
pub fn peek(&mut self, index: &I) -> Option<&W::Item> {
if self.peeked.is_none() {
self.peeked = self.walk.next(index);
}
self.peeked.as_ref()
}
pub fn next_if(
&mut self,
index: &I,
predicate: impl FnOnce(&W::Item) -> bool,
) -> Option<W::Item> {
match self.next(index) {
Some(item) if predicate(&item) => Some(item),
other => {
assert!(self.peeked.is_none());
self.peeked = other;
None
}
}
}
}
impl<I: ?Sized, W: RevWalk<I>> RevWalk<I> for PeekableRevWalk<I, W> {
type Item = W::Item;
fn next(&mut self, index: &I) -> Option<Self::Item> {
self.peeked.take().or_else(|| self.walk.next(index))
}
}
#[derive(Clone, Debug)]
#[must_use]
pub(super) struct RevWalkBorrowedIndexIter<'a, I: ?Sized, W> {
index: &'a I,
walk: W,
}
impl<I: ?Sized, W> RevWalkBorrowedIndexIter<'_, I, W> {
pub fn detach(self) -> W {
self.walk
}
}
impl<I: ?Sized, W: RevWalk<I>> Iterator for RevWalkBorrowedIndexIter<'_, I, W> {
type Item = W::Item;
fn next(&mut self) -> Option<Self::Item> {
self.walk.next(self.index)
}
}
impl<I: ?Sized, W: RevWalk<I>> FusedIterator for RevWalkBorrowedIndexIter<'_, I, W> {}
#[derive(Clone, Debug)]
#[must_use]
pub(super) struct RevWalkOwnedIndexIter<I, W> {
index: I,
walk: W,
}
impl<I, W: RevWalk<I>> Iterator for RevWalkOwnedIndexIter<I, W> {
type Item = W::Item;
fn next(&mut self) -> Option<Self::Item> {
self.walk.next(&self.index)
}
}
impl<I, W: RevWalk<I>> FusedIterator for RevWalkOwnedIndexIter<I, W> {}
pub(super) trait RevWalkIndex {
type Position: Copy + Ord;
type AdjacentPositions: IntoIterator<Item = Self::Position> + AsRef<[Self::Position]>;
fn adjacent_positions(&self, pos: Self::Position) -> Self::AdjacentPositions;
}
impl RevWalkIndex for CompositeIndex {
type Position = GlobalCommitPosition;
type AdjacentPositions = SmallGlobalCommitPositionsVec;
fn adjacent_positions(&self, pos: Self::Position) -> Self::AdjacentPositions {
self.commits().entry_by_pos(pos).parent_positions()
}
}
#[derive(Clone)]
pub(super) struct RevWalkDescendantsIndex {
children_map: HashMap<GlobalCommitPosition, DescendantIndexPositionsVec>,
}
type DescendantIndexPositionsVec = SmallVec<[Reverse<GlobalCommitPosition>; 4]>;
impl RevWalkDescendantsIndex {
fn build(
index: &CompositeCommitIndex,
positions: impl IntoIterator<Item = GlobalCommitPosition>,
) -> Self {
let mut children_map: HashMap<GlobalCommitPosition, DescendantIndexPositionsVec> =
HashMap::new();
for pos in positions {
children_map.entry(pos).or_default(); for parent_pos in index.entry_by_pos(pos).parent_positions() {
let parent = children_map.entry(parent_pos).or_default();
parent.push(Reverse(pos));
}
}
Self { children_map }
}
fn contains_pos(&self, pos: GlobalCommitPosition) -> bool {
self.children_map.contains_key(&pos)
}
}
impl RevWalkIndex for RevWalkDescendantsIndex {
type Position = Reverse<GlobalCommitPosition>;
type AdjacentPositions = DescendantIndexPositionsVec;
fn adjacent_positions(&self, pos: Self::Position) -> Self::AdjacentPositions {
self.children_map[&pos.0].clone()
}
}
#[derive(Clone)]
#[must_use]
pub(super) struct RevWalkBuilder<'a> {
index: &'a CompositeIndex,
wanted: Vec<GlobalCommitPosition>,
unwanted: Vec<GlobalCommitPosition>,
wanted_parents_range: Range<u32>,
}
impl<'a> RevWalkBuilder<'a> {
pub fn new(index: &'a CompositeIndex) -> Self {
Self {
index,
wanted: Vec::new(),
unwanted: Vec::new(),
wanted_parents_range: PARENTS_RANGE_FULL,
}
}
pub fn wanted_heads(mut self, positions: Vec<GlobalCommitPosition>) -> Self {
self.wanted = positions;
self
}
pub fn wanted_parents_range(mut self, wanted_parents_range: Range<u32>) -> Self {
assert!(wanted_parents_range.start <= wanted_parents_range.end);
self.wanted_parents_range = wanted_parents_range;
self
}
pub fn unwanted_roots(mut self, positions: Vec<GlobalCommitPosition>) -> Self {
self.unwanted = positions;
self
}
pub fn ancestors(self) -> RevWalkAncestors<'a> {
self.ancestors_with_min_pos(GlobalCommitPosition::MIN)
}
fn ancestors_with_min_pos(self, min_pos: GlobalCommitPosition) -> RevWalkAncestors<'a> {
let index = self.index;
let wanted_parents_range = self.wanted_parents_range;
let mut wanted_queue = RevWalkQueue::with_min_pos(min_pos);
let mut unwanted_queue = RevWalkQueue::with_min_pos(min_pos);
wanted_queue.extend(self.wanted, ());
unwanted_queue.extend(self.unwanted, ());
RevWalkBorrowedIndexIter {
index,
walk: RevWalkImpl {
wanted_queue,
unwanted_queue,
wanted_parents_range,
},
}
}
pub fn ancestors_filtered_by_generation(
self,
generation_range: Range<u32>,
) -> RevWalkAncestorsGenerationRange<'a> {
let index = self.index;
let wanted_parents_range = self.wanted_parents_range;
let mut wanted_queue = RevWalkQueue::with_min_pos(GlobalCommitPosition::MIN);
let mut unwanted_queue = RevWalkQueue::with_min_pos(GlobalCommitPosition::MIN);
let item_range = RevWalkItemGenerationRange::from_filter_range(generation_range.clone());
wanted_queue.extend(self.wanted, Reverse(item_range));
unwanted_queue.extend(self.unwanted, ());
RevWalkBorrowedIndexIter {
index,
walk: RevWalkGenerationRangeImpl {
wanted_queue,
unwanted_queue,
wanted_parents_range,
generation_end: generation_range.end,
},
}
}
pub fn ancestors_until_roots(
self,
root_positions: impl IntoIterator<Item = GlobalCommitPosition>,
) -> RevWalkAncestors<'a> {
let min_pos = root_positions
.into_iter()
.min()
.unwrap_or(GlobalCommitPosition::MAX);
self.ancestors_with_min_pos(min_pos)
}
pub fn descendants(
self,
root_positions: HashSet<GlobalCommitPosition>,
) -> RevWalkDescendants<'a> {
let index = self.index;
let candidate_positions = self
.ancestors_until_roots(root_positions.iter().copied())
.collect();
RevWalkBorrowedIndexIter {
index,
walk: RevWalkDescendantsImpl {
candidate_positions,
root_positions,
reachable_positions: HashSet::new(),
},
}
}
pub fn descendants_filtered_by_generation(
self,
root_positions: Vec<GlobalCommitPosition>,
generation_range: Range<u32>,
) -> RevWalkDescendantsGenerationRange {
let index = self.index;
let positions = self.ancestors_until_roots(root_positions.iter().copied());
let descendants_index = RevWalkDescendantsIndex::build(index.commits(), positions);
let mut wanted_queue = RevWalkQueue::with_min_pos(Reverse(GlobalCommitPosition::MAX));
let unwanted_queue = RevWalkQueue::with_min_pos(Reverse(GlobalCommitPosition::MAX));
let item_range = RevWalkItemGenerationRange::from_filter_range(generation_range.clone());
for pos in root_positions {
if descendants_index.contains_pos(pos) {
wanted_queue.push(Reverse(pos), Reverse(item_range));
}
}
RevWalkOwnedIndexIter {
index: descendants_index,
walk: RevWalkGenerationRangeImpl {
wanted_queue,
unwanted_queue,
wanted_parents_range: PARENTS_RANGE_FULL,
generation_end: generation_range.end,
},
}
}
}
pub(super) type RevWalkAncestors<'a> =
RevWalkBorrowedIndexIter<'a, CompositeIndex, RevWalkImpl<GlobalCommitPosition>>;
#[derive(Clone)]
#[must_use]
pub(super) struct RevWalkImpl<P> {
wanted_queue: RevWalkQueue<P, ()>,
unwanted_queue: RevWalkQueue<P, ()>,
wanted_parents_range: Range<u32>,
}
impl<I: RevWalkIndex + ?Sized> RevWalk<I> for RevWalkImpl<I::Position> {
type Item = I::Position;
fn next(&mut self, index: &I) -> Option<Self::Item> {
while let Some(item) = self.wanted_queue.pop() {
self.wanted_queue.skip_while_eq(&item.pos);
if flush_queue_until(&mut self.unwanted_queue, index, item.pos).is_some() {
continue;
}
let parents = index.adjacent_positions(item.pos);
self.wanted_queue.extend(
filter_slice_by_range(parents.as_ref(), &self.wanted_parents_range)
.iter()
.copied(),
(),
);
return Some(item.pos);
}
None
}
}
pub(super) type RevWalkAncestorsGenerationRange<'a> =
RevWalkBorrowedIndexIter<'a, CompositeIndex, RevWalkGenerationRangeImpl<GlobalCommitPosition>>;
pub(super) type RevWalkDescendantsGenerationRange = RevWalkOwnedIndexIter<
RevWalkDescendantsIndex,
RevWalkGenerationRangeImpl<Reverse<GlobalCommitPosition>>,
>;
#[derive(Clone)]
#[must_use]
pub(super) struct RevWalkGenerationRangeImpl<P> {
wanted_queue: RevWalkQueue<P, Reverse<RevWalkItemGenerationRange>>,
unwanted_queue: RevWalkQueue<P, ()>,
wanted_parents_range: Range<u32>,
generation_end: u32,
}
impl<P: Copy + Ord> RevWalkGenerationRangeImpl<P> {
fn enqueue_wanted_adjacents<I>(
&mut self,
index: &I,
pos: P,
generation: RevWalkItemGenerationRange,
) where
I: RevWalkIndex<Position = P> + ?Sized,
{
if generation.start + 1 >= self.generation_end {
return;
}
let succ_generation = RevWalkItemGenerationRange {
start: generation.start + 1,
end: generation.end.saturating_add(1),
};
let parents = index.adjacent_positions(pos);
self.wanted_queue.extend(
filter_slice_by_range(parents.as_ref(), &self.wanted_parents_range)
.iter()
.copied(),
Reverse(succ_generation),
);
}
}
impl<I: RevWalkIndex + ?Sized> RevWalk<I> for RevWalkGenerationRangeImpl<I::Position> {
type Item = I::Position;
fn next(&mut self, index: &I) -> Option<Self::Item> {
while let Some(item) = self.wanted_queue.pop() {
if flush_queue_until(&mut self.unwanted_queue, index, item.pos).is_some() {
self.wanted_queue.skip_while_eq(&item.pos);
continue;
}
let Reverse(mut pending_gen) = item.value;
let mut some_in_range = pending_gen.contains_end(self.generation_end);
while let Some(x) = self.wanted_queue.pop_eq(&item.pos) {
let Reverse(generation) = x.value;
some_in_range |= generation.contains_end(self.generation_end);
pending_gen = if let Some(merged) = pending_gen.try_merge_end(generation) {
merged
} else {
self.enqueue_wanted_adjacents(index, item.pos, pending_gen);
generation
};
}
self.enqueue_wanted_adjacents(index, item.pos, pending_gen);
if some_in_range {
return Some(item.pos);
}
}
None
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
struct RevWalkItemGenerationRange {
start: u32,
end: u32,
}
impl RevWalkItemGenerationRange {
fn from_filter_range(range: Range<u32>) -> Self {
Self {
start: 0,
end: u32::saturating_sub(range.end, range.start),
}
}
#[must_use]
fn try_merge_end(self, other: Self) -> Option<Self> {
(other.start <= self.end).then(|| Self {
start: self.start,
end: max(self.end, other.end),
})
}
#[must_use]
fn contains_end(self, end: u32) -> bool {
self.start < end && end <= self.end
}
}
fn flush_queue_until<I: RevWalkIndex + ?Sized>(
queue: &mut RevWalkQueue<I::Position, ()>,
index: &I,
bottom_pos: I::Position,
) -> Option<RevWalkWorkItem<I::Position, ()>> {
while let Some(item) = queue.pop_if(|x| x.pos >= bottom_pos) {
queue.skip_while_eq(&item.pos);
queue.extend(index.adjacent_positions(item.pos), ());
if item.pos == bottom_pos {
return Some(item);
}
}
None
}
pub(super) type RevWalkDescendants<'a> =
RevWalkBorrowedIndexIter<'a, CompositeIndex, RevWalkDescendantsImpl>;
#[derive(Clone)]
#[must_use]
pub(super) struct RevWalkDescendantsImpl {
candidate_positions: Vec<GlobalCommitPosition>,
root_positions: HashSet<GlobalCommitPosition>,
reachable_positions: HashSet<GlobalCommitPosition>,
}
impl RevWalkDescendants<'_> {
pub fn collect_positions_set(mut self) -> HashSet<GlobalCommitPosition> {
self.by_ref().for_each(drop);
self.walk.reachable_positions
}
}
impl RevWalk<CompositeIndex> for RevWalkDescendantsImpl {
type Item = GlobalCommitPosition;
fn next(&mut self, index: &CompositeIndex) -> Option<Self::Item> {
let index = index.commits();
while let Some(candidate_pos) = self.candidate_positions.pop() {
if self.root_positions.contains(&candidate_pos)
|| index
.entry_by_pos(candidate_pos)
.parent_positions()
.iter()
.any(|parent_pos| self.reachable_positions.contains(parent_pos))
{
self.reachable_positions.insert(candidate_pos);
return Some(candidate_pos);
}
}
None
}
}
pub(super) fn filter_slice_by_range<'a, T: Copy>(slice: &'a [T], range: &Range<u32>) -> &'a [T] {
let start = (range.start as usize).min(slice.len());
let end = (range.end as usize).min(slice.len());
&slice[start..end]
}
#[cfg(test)]
#[rustversion::attr(
since(1.89),
expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
)]
mod tests {
use itertools::Itertools as _;
use super::super::composite::AsCompositeIndex as _;
use super::super::mutable::DefaultMutableIndex;
use super::*;
use crate::backend::ChangeId;
use crate::backend::CommitId;
use crate::default_index::readonly::FieldLengths;
const TEST_FIELD_LENGTHS: FieldLengths = FieldLengths {
commit_id: 3,
change_id: 16,
};
fn change_id_generator() -> impl FnMut() -> ChangeId {
let mut iter = (1_u128..).map(|n| ChangeId::new(n.to_le_bytes().into()));
move || iter.next().unwrap()
}
fn to_positions_vec(
index: &CompositeIndex,
commit_ids: &[CommitId],
) -> Vec<GlobalCommitPosition> {
commit_ids
.iter()
.map(|id| index.commits().commit_id_to_pos(id).unwrap())
.collect()
}
#[test]
fn test_filter_map_rev_walk() {
let source = EagerRevWalk::new(vec![0, 1, 2, 3, 4].into_iter());
let mut filtered = source.filter_map(|_, v| (v & 1 == 0).then_some(v + 5));
assert_eq!(filtered.next(&()), Some(5));
assert_eq!(filtered.next(&()), Some(7));
assert_eq!(filtered.next(&()), Some(9));
assert_eq!(filtered.next(&()), None);
assert_eq!(filtered.next(&()), None);
}
#[test]
fn test_map_rev_walk() {
let source = EagerRevWalk::new(vec![0, 1, 2].into_iter());
let mut mapped = source.map(|_, v| v + 5);
assert_eq!(mapped.next(&()), Some(5));
assert_eq!(mapped.next(&()), Some(6));
assert_eq!(mapped.next(&()), Some(7));
assert_eq!(mapped.next(&()), None);
assert_eq!(mapped.next(&()), None);
}
#[test]
fn test_peekable_rev_walk() {
let source = EagerRevWalk::new(vec![0, 1, 2, 3].into_iter());
let mut peekable = source.peekable();
assert_eq!(peekable.peek(&()), Some(&0));
assert_eq!(peekable.peek(&()), Some(&0));
assert_eq!(peekable.next(&()), Some(0));
assert_eq!(peekable.peeked, None);
assert_eq!(peekable.next_if(&(), |&v| v == 2), None);
assert_eq!(peekable.next(&()), Some(1));
assert_eq!(peekable.next_if(&(), |&v| v == 2), Some(2));
assert_eq!(peekable.peeked, None);
assert_eq!(peekable.peek(&()), Some(&3));
assert_eq!(peekable.next_if(&(), |&v| v == 3), Some(3));
assert_eq!(peekable.peeked, None);
assert_eq!(peekable.next(&()), None);
assert_eq!(peekable.next(&()), None);
let source = EagerRevWalk::new((vec![] as Vec<i32>).into_iter());
let mut peekable = source.peekable();
assert_eq!(peekable.peek(&()), None);
assert_eq!(peekable.next(&()), None);
}
#[test]
fn test_walk_ancestors() {
let mut new_change_id = change_id_generator();
let mut index = DefaultMutableIndex::full(TEST_FIELD_LENGTHS);
let id_0 = CommitId::from_hex("000000");
let id_1 = CommitId::from_hex("111111");
let id_2 = CommitId::from_hex("222222");
let id_3 = CommitId::from_hex("333333");
let id_4 = CommitId::from_hex("444444");
let id_5 = CommitId::from_hex("555555");
index.add_commit_data(id_0.clone(), new_change_id(), &[]);
index.add_commit_data(id_1.clone(), new_change_id(), &[id_0.clone()]);
index.add_commit_data(id_2.clone(), new_change_id(), &[id_0.clone()]);
index.add_commit_data(id_3.clone(), new_change_id(), &[id_2.clone()]);
index.add_commit_data(id_4.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_5.clone(), new_change_id(), &[id_4.clone(), id_2.clone()]);
let walk_commit_ids = |wanted: &[CommitId], unwanted: &[CommitId]| {
let index = index.as_composite();
RevWalkBuilder::new(index)
.wanted_heads(to_positions_vec(index, wanted))
.unwanted_roots(to_positions_vec(index, unwanted))
.ancestors()
.map(|pos| index.commits().entry_by_pos(pos).commit_id())
.collect_vec()
};
assert!(walk_commit_ids(&[], &[]).is_empty());
assert_eq!(
walk_commit_ids(&[id_4.clone()], &[]),
vec![id_4.clone(), id_1.clone(), id_0.clone()]
);
assert_eq!(walk_commit_ids(&[id_0.clone()], &[id_0.clone()]), vec![]);
assert_eq!(
walk_commit_ids(&[id_0.clone(), id_0.clone()], &[]),
vec![id_0.clone()]
);
assert_eq!(
walk_commit_ids(&[id_0.clone(), id_1.clone()], &[]),
vec![id_1.clone(), id_0.clone()]
);
assert_eq!(
walk_commit_ids(&[id_2.clone()], &[id_1.clone()]),
vec![id_2.clone()]
);
assert_eq!(
walk_commit_ids(&[id_1.clone()], &[id_2.clone()]),
vec![id_1.clone()]
);
assert_eq!(
walk_commit_ids(&[id_1.clone(), id_2.clone()], &[]),
vec![id_2.clone(), id_1.clone(), id_0.clone()]
);
assert_eq!(
walk_commit_ids(&[id_2.clone(), id_1.clone()], &[]),
vec![id_2.clone(), id_1.clone(), id_0]
);
assert_eq!(
walk_commit_ids(&[id_5.clone(), id_3.clone()], &[id_2]),
vec![id_5, id_4, id_3, id_1]
);
}
#[test]
fn test_walk_ancestors_until_roots() {
let mut new_change_id = change_id_generator();
let mut index = DefaultMutableIndex::full(TEST_FIELD_LENGTHS);
let id_0 = CommitId::from_hex("000000");
let id_1 = CommitId::from_hex("111111");
let id_2 = CommitId::from_hex("222222");
let id_3 = CommitId::from_hex("333333");
let id_4 = CommitId::from_hex("444444");
let id_5 = CommitId::from_hex("555555");
let id_6 = CommitId::from_hex("666666");
let id_7 = CommitId::from_hex("777777");
index.add_commit_data(id_0.clone(), new_change_id(), &[]);
index.add_commit_data(id_1.clone(), new_change_id(), &[id_0.clone()]);
index.add_commit_data(id_2.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_3.clone(), new_change_id(), &[id_2.clone()]);
index.add_commit_data(id_4.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_5.clone(), new_change_id(), &[id_4.clone()]);
index.add_commit_data(id_6.clone(), new_change_id(), &[id_5.clone()]);
index.add_commit_data(id_7.clone(), new_change_id(), &[id_3.clone()]);
let index = index.as_composite();
let make_iter = |heads: &[CommitId], roots: &[CommitId]| {
RevWalkBuilder::new(index)
.wanted_heads(to_positions_vec(index, heads))
.ancestors_until_roots(to_positions_vec(index, roots))
};
let to_commit_id = |pos| index.commits().entry_by_pos(pos).commit_id();
let mut iter = make_iter(&[id_6.clone(), id_7.clone()], &[id_3.clone()]);
assert_eq!(iter.walk.wanted_queue.len(), 2);
assert_eq!(iter.next().map(to_commit_id), Some(id_7.clone()));
assert_eq!(iter.next().map(to_commit_id), Some(id_6.clone()));
assert_eq!(iter.next().map(to_commit_id), Some(id_5.clone()));
assert_eq!(iter.walk.wanted_queue.len(), 2);
assert_eq!(iter.next().map(to_commit_id), Some(id_4.clone()));
assert_eq!(iter.walk.wanted_queue.len(), 1); assert_eq!(iter.next().map(to_commit_id), Some(id_3.clone()));
assert_eq!(iter.walk.wanted_queue.len(), 0); assert!(iter.next().is_none());
let iter = make_iter(&[id_6.clone(), id_7.clone(), id_2.clone()], &[id_3.clone()]);
assert_eq!(iter.walk.wanted_queue.len(), 2);
let iter = make_iter(&[id_6.clone(), id_7.clone()], &[]);
assert_eq!(iter.walk.wanted_queue.len(), 0); }
#[test]
fn test_walk_ancestors_filtered_by_generation() {
let mut new_change_id = change_id_generator();
let mut index = DefaultMutableIndex::full(TEST_FIELD_LENGTHS);
let id_0 = CommitId::from_hex("000000");
let id_1 = CommitId::from_hex("111111");
let id_2 = CommitId::from_hex("222222");
let id_3 = CommitId::from_hex("333333");
let id_4 = CommitId::from_hex("444444");
let id_5 = CommitId::from_hex("555555");
let id_6 = CommitId::from_hex("666666");
let id_7 = CommitId::from_hex("777777");
let id_8 = CommitId::from_hex("888888");
index.add_commit_data(id_0.clone(), new_change_id(), &[]);
index.add_commit_data(id_1.clone(), new_change_id(), &[id_0.clone()]);
index.add_commit_data(id_2.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_3.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_4.clone(), new_change_id(), &[id_2.clone()]);
index.add_commit_data(id_5.clone(), new_change_id(), &[id_4.clone(), id_3.clone()]);
index.add_commit_data(id_6.clone(), new_change_id(), &[id_5.clone()]);
index.add_commit_data(id_7.clone(), new_change_id(), &[id_4.clone()]);
index.add_commit_data(id_8.clone(), new_change_id(), &[id_7.clone()]);
let walk_commit_ids = |wanted: &[CommitId], unwanted: &[CommitId], range: Range<u32>| {
let index = index.as_composite();
RevWalkBuilder::new(index)
.wanted_heads(to_positions_vec(index, wanted))
.unwanted_roots(to_positions_vec(index, unwanted))
.ancestors_filtered_by_generation(range)
.map(|pos| index.commits().entry_by_pos(pos).commit_id())
.collect_vec()
};
assert_eq!(walk_commit_ids(&[&id_8].map(Clone::clone), &[], 0..0), []);
assert_eq!(
walk_commit_ids(&[&id_8].map(Clone::clone), &[], Range { start: 2, end: 1 }),
[]
);
assert_eq!(
walk_commit_ids(&[&id_2].map(Clone::clone), &[], 0..3),
[&id_2, &id_1, &id_0].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_6].map(Clone::clone), &[], 2..4),
[&id_4, &id_3, &id_2, &id_1].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_5].map(Clone::clone), &[], 2..3),
[&id_2, &id_1].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_5, &id_7].map(Clone::clone), &[], 2..3),
[&id_2, &id_1].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_7, &id_8].map(Clone::clone), &[], 0..2),
[&id_8, &id_7, &id_4].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_6, &id_7].map(Clone::clone), &[], 0..3),
[&id_7, &id_6, &id_5, &id_4, &id_3, &id_2].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_6, &id_7].map(Clone::clone), &[], 2..3),
[&id_4, &id_3, &id_2].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_5].map(Clone::clone), &[&id_2].map(Clone::clone), 1..5),
[&id_4, &id_3].map(Clone::clone)
);
}
#[test]
#[expect(clippy::redundant_clone)] fn test_walk_ancestors_filtered_by_generation_range_merging() {
let mut new_change_id = change_id_generator();
let mut index = DefaultMutableIndex::full(TEST_FIELD_LENGTHS);
let ids = (0..11)
.map(|n| CommitId::try_from_hex(format!("{n:06x}")).unwrap())
.collect_vec();
index.add_commit_data(ids[0].clone(), new_change_id(), &[]);
for i in 1..ids.len() {
index.add_commit_data(ids[i].clone(), new_change_id(), &[ids[i - 1].clone()]);
}
let id_branch5_0 = CommitId::from_hex("050000");
let id_branch5_1 = CommitId::from_hex("050001");
index.add_commit_data(id_branch5_0.clone(), new_change_id(), &[ids[5].clone()]);
index.add_commit_data(
id_branch5_1.clone(),
new_change_id(),
&[id_branch5_0.clone()],
);
let walk_commit_ids = |wanted: &[CommitId], range: Range<u32>| {
let index = index.as_composite();
RevWalkBuilder::new(index)
.wanted_heads(to_positions_vec(index, wanted))
.ancestors_filtered_by_generation(range)
.map(|pos| index.commits().entry_by_pos(pos).commit_id())
.collect_vec()
};
assert_eq!(
walk_commit_ids(&[&ids[9], &ids[6]].map(Clone::clone), 4..6),
[&ids[5], &ids[4], &ids[2], &ids[1]].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&ids[10], &ids[7], &ids[6]].map(Clone::clone), 5..7),
[&ids[5], &ids[4], &ids[2], &ids[1], &ids[0]].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(
&[&ids[8], &ids[7], &ids[6], &id_branch5_1].map(Clone::clone),
5..6
),
[&ids[3], &ids[2], &ids[1]].map(Clone::clone)
);
}
#[test]
fn test_walk_descendants_filtered_by_generation() {
let mut new_change_id = change_id_generator();
let mut index = DefaultMutableIndex::full(TEST_FIELD_LENGTHS);
let id_0 = CommitId::from_hex("000000");
let id_1 = CommitId::from_hex("111111");
let id_2 = CommitId::from_hex("222222");
let id_3 = CommitId::from_hex("333333");
let id_4 = CommitId::from_hex("444444");
let id_5 = CommitId::from_hex("555555");
let id_6 = CommitId::from_hex("666666");
let id_7 = CommitId::from_hex("777777");
let id_8 = CommitId::from_hex("888888");
index.add_commit_data(id_0.clone(), new_change_id(), &[]);
index.add_commit_data(id_1.clone(), new_change_id(), &[id_0.clone()]);
index.add_commit_data(id_2.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_3.clone(), new_change_id(), &[id_1.clone()]);
index.add_commit_data(id_4.clone(), new_change_id(), &[id_2.clone()]);
index.add_commit_data(id_5.clone(), new_change_id(), &[id_4.clone(), id_3.clone()]);
index.add_commit_data(id_6.clone(), new_change_id(), &[id_5.clone()]);
index.add_commit_data(id_7.clone(), new_change_id(), &[id_4.clone()]);
index.add_commit_data(id_8.clone(), new_change_id(), &[id_7.clone()]);
let visible_heads = [&id_6, &id_8].map(Clone::clone);
let walk_commit_ids = |roots: &[CommitId], heads: &[CommitId], range: Range<u32>| {
let index = index.as_composite();
RevWalkBuilder::new(index)
.wanted_heads(to_positions_vec(index, heads))
.descendants_filtered_by_generation(to_positions_vec(index, roots), range)
.map(|Reverse(pos)| index.commits().entry_by_pos(pos).commit_id())
.collect_vec()
};
assert_eq!(
walk_commit_ids(&[&id_0].map(Clone::clone), &visible_heads, 0..0),
[]
);
assert_eq!(
walk_commit_ids(
&[&id_8].map(Clone::clone),
&visible_heads,
Range { start: 2, end: 1 }
),
[]
);
assert_eq!(
walk_commit_ids(&[&id_0].map(Clone::clone), &visible_heads, 0..u32::MAX),
[
&id_0, &id_1, &id_2, &id_3, &id_4, &id_5, &id_6, &id_7, &id_8
]
.map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_3].map(Clone::clone), &visible_heads, 0..3),
[&id_3, &id_5, &id_6].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_0].map(Clone::clone), &visible_heads, 2..4),
[&id_2, &id_3, &id_4, &id_5].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_1].map(Clone::clone), &visible_heads, 2..3),
[&id_4, &id_5].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_2, &id_3].map(Clone::clone), &visible_heads, 2..3),
[&id_5, &id_6, &id_7].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_2, &id_4].map(Clone::clone), &visible_heads, 0..2),
[&id_2, &id_4, &id_5, &id_7].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_2, &id_3].map(Clone::clone), &visible_heads, 0..3),
[&id_2, &id_3, &id_4, &id_5, &id_6, &id_7].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(&[&id_2, &id_3].map(Clone::clone), &visible_heads, 2..3),
[&id_5, &id_6, &id_7].map(Clone::clone)
);
assert_eq!(
walk_commit_ids(
&[&id_2, &id_3].map(Clone::clone),
&[&id_8].map(Clone::clone),
0..3
),
[&id_2, &id_4, &id_7].map(Clone::clone)
);
}
}