// Copyright 2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. #![allow(missing_docs)] use std::cmp::max;use std::cmp::Ordering;use std::cmp::Reverse;use std::collections::binary_heap;use std::collections::BinaryHeap;use std::collections::HashSet;use std::iter;use std::mem;use std::sync::Arc;use std::sync::Mutex; use itertools::Itertools as _;use ref_cast::ref_cast_custom;use ref_cast::RefCastCustom; use super::entry::IndexEntry;use super::entry::IndexPosition;use super::entry::LocalPosition;use super::entry::SmallIndexPositionsVec;use super::entry::SmallLocalPositionsVec;use super::readonly::ReadonlyIndexSegment;use super::rev_walk::AncestorsBitSet;use super::revset_engine;use crate::backend::ChangeId;use crate::backend::CommitId;use crate::hex_util;use crate::index::AllHeadsForGcUnsupported;use crate::index::ChangeIdIndex;use crate::index::Index;use crate::index::IndexError;use crate::object_id::HexPrefix;use crate::object_id::ObjectId as _;use crate::object_id::PrefixResolution;use crate::revset::ResolvedExpression;use crate::revset::Revset;use crate::revset::RevsetEvaluationError;use crate::store::Store; pub(super) trait IndexSegment: Send + Sync { fn num_parent_commits(&self) -> u32; fn num_local_commits(&self) -> u32; fn parent_file(&self) -> Option<&Arc<ReadonlyIndexSegment>>; fn name(&self) -> Option<String>; fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<LocalPosition>; /// Suppose the given `commit_id` exists, returns the previous and next /// commit ids in lexicographical order. fn resolve_neighbor_commit_ids( &self, commit_id: &CommitId, ) -> (Option<CommitId>, Option<CommitId>); fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId>; fn resolve_neighbor_change_ids( &self, change_id: &ChangeId, ) -> (Option<ChangeId>, Option<ChangeId>); fn resolve_change_id_prefix( &self, prefix: &HexPrefix, ) -> PrefixResolution<(ChangeId, SmallLocalPositionsVec)>; fn generation_number(&self, local_pos: LocalPosition) -> u32; fn commit_id(&self, local_pos: LocalPosition) -> CommitId; fn change_id(&self, local_pos: LocalPosition) -> ChangeId; fn num_parents(&self, local_pos: LocalPosition) -> u32; fn parent_positions(&self, local_pos: LocalPosition) -> SmallIndexPositionsVec;} pub(super) type DynIndexSegment = dyn IndexSegment; /// Abstraction over owned and borrowed types that can be cheaply converted to/// a `CompositeIndex` reference.pub trait AsCompositeIndex { /// Returns reference wrapper that provides global access to this index. fn as_composite(&self) -> &CompositeIndex;} impl<T: AsCompositeIndex + ?Sized> AsCompositeIndex for &T { fn as_composite(&self) -> &CompositeIndex { <T as AsCompositeIndex>::as_composite(self) }} impl<T: AsCompositeIndex + ?Sized> AsCompositeIndex for &mut T { fn as_composite(&self) -> &CompositeIndex { <T as AsCompositeIndex>::as_composite(self) }} /// `CompositeIndex` provides an index of both commit IDs and change IDs.////// We refer to this as a composite index because it's a composite of multiple/// nested index segments where each parent segment is roughly twice as large/// its child. segment. This provides a good balance between read and write/// performance.// Reference wrapper that provides global access to nested index segments.#[derive(RefCastCustom)]#[repr(transparent)]pub struct CompositeIndex(DynIndexSegment); impl CompositeIndex { #[ref_cast_custom] pub(super) const fn new(segment: &DynIndexSegment) -> &Self; /// Iterates parent and its ancestor readonly index segments. pub(super) fn ancestor_files_without_local( &self, ) -> impl Iterator<Item = &Arc<ReadonlyIndexSegment>> { let parent_file = self.0.parent_file(); iter::successors(parent_file, |file| file.parent_file()) } /// Iterates self and its ancestor index segments. pub(super) fn ancestor_index_segments(&self) -> impl Iterator<Item = &DynIndexSegment> { iter::once(&self.0).chain( self.ancestor_files_without_local() .map(|file| file.as_ref() as &DynIndexSegment), ) } pub fn num_commits(&self) -> u32 { self.0.num_parent_commits() + self.0.num_local_commits() } pub fn stats(&self) -> IndexStats { let num_commits = self.num_commits(); let mut num_merges = 0; let mut max_generation_number = 0; let mut change_ids = HashSet::new(); for pos in 0..num_commits { let entry = self.entry_by_pos(IndexPosition(pos)); max_generation_number = max(max_generation_number, entry.generation_number()); if entry.num_parents() > 1 { num_merges += 1; } change_ids.insert(entry.change_id()); } let num_heads = u32::try_from(self.all_heads_pos().count()).unwrap(); let mut levels = self .ancestor_index_segments() .map(|segment| IndexLevelStats { num_commits: segment.num_local_commits(), name: segment.name(), }) .collect_vec(); levels.reverse(); IndexStats { num_commits, num_merges, max_generation_number, num_heads, num_changes: change_ids.len().try_into().unwrap(), levels, } } pub fn entry_by_pos(&self, pos: IndexPosition) -> IndexEntry<'_> { self.ancestor_index_segments() .find_map(|segment| { u32::checked_sub(pos.0, segment.num_parent_commits()) .map(|local_pos| IndexEntry::new(segment, pos, LocalPosition(local_pos))) }) .unwrap() } pub fn entry_by_id(&self, commit_id: &CommitId) -> Option<IndexEntry<'_>> { self.ancestor_index_segments().find_map(|segment| { let local_pos = segment.commit_id_to_pos(commit_id)?; let pos = IndexPosition(local_pos.0 + segment.num_parent_commits()); Some(IndexEntry::new(segment, pos, local_pos)) }) } pub fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<IndexPosition> { self.ancestor_index_segments().find_map(|segment| { let LocalPosition(local_pos) = segment.commit_id_to_pos(commit_id)?; Some(IndexPosition(local_pos + segment.num_parent_commits())) }) } /// Suppose the given `commit_id` exists, returns the previous and next /// commit ids in lexicographical order. pub(super) fn resolve_neighbor_commit_ids( &self, commit_id: &CommitId, ) -> (Option<CommitId>, Option<CommitId>) { self.ancestor_index_segments() .map(|segment| segment.resolve_neighbor_commit_ids(commit_id)) .reduce(|(acc_prev_id, acc_next_id), (prev_id, next_id)| { ( acc_prev_id.into_iter().chain(prev_id).max(), acc_next_id.into_iter().chain(next_id).min(), ) }) .unwrap() } /// Suppose the given `change_id` exists, returns the minimum prefix length /// to disambiguate it within all the indexed ids including hidden ones. pub(super) fn shortest_unique_change_id_prefix_len(&self, change_id: &ChangeId) -> usize { let (prev_id, next_id) = self.resolve_neighbor_change_ids(change_id); itertools::chain(prev_id, next_id) .map(|id| hex_util::common_hex_len(change_id.as_bytes(), id.as_bytes()) + 1) .max() .unwrap_or(0) } /// Suppose the given `change_id` exists, returns the previous and next /// change ids in lexicographical order. The returned change ids may be /// hidden. pub(super) fn resolve_neighbor_change_ids( &self, change_id: &ChangeId, ) -> (Option<ChangeId>, Option<ChangeId>) { self.ancestor_index_segments() .map(|segment| segment.resolve_neighbor_change_ids(change_id)) .reduce(|(acc_prev_id, acc_next_id), (prev_id, next_id)| { ( acc_prev_id.into_iter().chain(prev_id).max(), acc_next_id.into_iter().chain(next_id).min(), ) }) .unwrap() } /// Resolves the given change id `prefix` to the associated entries. The /// returned entries may be hidden. /// /// The returned index positions are sorted in ascending order. pub(super) fn resolve_change_id_prefix( &self, prefix: &HexPrefix, ) -> PrefixResolution<(ChangeId, SmallIndexPositionsVec)> { use PrefixResolution::*; self.ancestor_index_segments() .fold(NoMatch, |acc_match, segment| { if acc_match == AmbiguousMatch { return acc_match; // avoid checking the parent file(s) } let to_global_pos = { let num_parent_commits = segment.num_parent_commits(); move |LocalPosition(pos)| IndexPosition(pos + num_parent_commits) }; // Similar to PrefixResolution::plus(), but merges matches of the same id. match (acc_match, segment.resolve_change_id_prefix(prefix)) { (NoMatch, local_match) => local_match.map(|(id, positions)| { (id, positions.into_iter().map(to_global_pos).collect()) }), (acc_match, NoMatch) => acc_match, (AmbiguousMatch, _) => AmbiguousMatch, (_, AmbiguousMatch) => AmbiguousMatch, (SingleMatch((id1, _)), SingleMatch((id2, _))) if id1 != id2 => AmbiguousMatch, (SingleMatch((id, mut acc_positions)), SingleMatch((_, local_positions))) => { acc_positions .insert_many(0, local_positions.into_iter().map(to_global_pos)); SingleMatch((id, acc_positions)) } } }) } pub(super) fn is_ancestor_pos( &self, ancestor_pos: IndexPosition, descendant_pos: IndexPosition, ) -> bool { let ancestor_generation = self.entry_by_pos(ancestor_pos).generation_number(); let mut work = vec![descendant_pos]; let mut visited = HashSet::new(); while let Some(descendant_pos) = work.pop() { let descendant_entry = self.entry_by_pos(descendant_pos); if descendant_pos == ancestor_pos { return true; } if !visited.insert(descendant_entry.position()) { continue; } if descendant_entry.generation_number() <= ancestor_generation { continue; } work.extend(descendant_entry.parent_positions()); } false } /// Computes the greatest common ancestors. /// /// The returned index positions are sorted in descending order. pub(super) fn common_ancestors_pos( &self, set1: Vec<IndexPosition>, set2: Vec<IndexPosition>, ) -> Vec<IndexPosition> { let mut items1 = BinaryHeap::from(set1); let mut items2 = BinaryHeap::from(set2); let mut result = Vec::new(); while let (Some(&pos1), Some(&pos2)) = (items1.peek(), items2.peek()) { match pos1.cmp(&pos2) { Ordering::Greater => shift_to_parents(&mut items1, &self.entry_by_pos(pos1)), Ordering::Less => shift_to_parents(&mut items2, &self.entry_by_pos(pos2)), Ordering::Equal => { result.push(pos1); dedup_pop(&mut items1).unwrap(); dedup_pop(&mut items2).unwrap(); } } } self.heads_pos(result) } pub(super) fn all_heads(&self) -> impl Iterator<Item = CommitId> + use<'_> { self.all_heads_pos() .map(move |pos| self.entry_by_pos(pos).commit_id()) } pub(super) fn all_heads_pos(&self) -> impl Iterator<Item = IndexPosition> + use<> { // TODO: can be optimized to use bit vec and leading/trailing_ones() let num_commits = self.num_commits(); let mut not_head: Vec<bool> = vec![false; num_commits as usize]; for pos in 0..num_commits { let entry = self.entry_by_pos(IndexPosition(pos)); for IndexPosition(parent_pos) in entry.parent_positions() { not_head[parent_pos as usize] = true; } } not_head .into_iter() .enumerate() .filter(|&(_, b)| !b) .map(|(i, _)| IndexPosition(u32::try_from(i).unwrap())) } /// Returns the subset of positions in `candidate_positions` which refer to /// entries that are heads in the repository. /// /// The `candidate_positions` must be sorted in descending order, and have /// no duplicates. The returned head positions are also sorted in descending /// order. pub fn heads_pos(&self, candidate_positions: Vec<IndexPosition>) -> Vec<IndexPosition> { debug_assert!(candidate_positions.is_sorted_by(|a, b| a > b)); let Some(min_generation) = candidate_positions .iter() .map(|&pos| self.entry_by_pos(pos).generation_number()) .min() else { return candidate_positions; }; // Iterate though the candidates by reverse index position, keeping track of the // ancestors of already-found heads. If a candidate is an ancestor of an // already-found head, then it can be removed. let mut parents = BinaryHeap::new(); let mut heads = Vec::new(); 'outer: for candidate in candidate_positions { while let Some(&parent) = parents.peek().filter(|&&parent| parent >= candidate) { let entry = self.entry_by_pos(parent); if entry.generation_number() <= min_generation { dedup_pop(&mut parents).unwrap(); } else { shift_to_parents(&mut parents, &entry); } if parent == candidate { // The candidate is an ancestor of an existing head, so we can skip it. continue 'outer; } } // No parents matched, so this commit is a head. let entry = self.entry_by_pos(candidate); parents.extend(entry.parent_positions()); heads.push(candidate); } heads } /// Find the heads of a range of positions `roots..heads`, applying a filter /// to the commits in the range. The heads are sorted in descending order. /// The filter will also be called in descending index position order. pub fn heads_from_range_and_filter<E>( &self, roots: Vec<IndexPosition>, heads: Vec<IndexPosition>, mut filter: impl FnMut(IndexPosition) -> Result<bool, E>, ) -> Result<Vec<IndexPosition>, E> { if heads.is_empty() { return Ok(heads); } let mut wanted_queue = BinaryHeap::from(heads); let mut unwanted_queue = BinaryHeap::from(roots); let mut found_heads = Vec::new(); while let Some(&pos) = wanted_queue.peek() { if shift_to_parents_until(&mut unwanted_queue, self, pos) { dedup_pop(&mut wanted_queue); continue; } let entry = self.entry_by_pos(pos); if filter(pos)? { dedup_pop(&mut wanted_queue); unwanted_queue.extend(entry.parent_positions()); found_heads.push(pos); } else { shift_to_parents(&mut wanted_queue, &entry); } } Ok(found_heads) } pub(super) fn evaluate_revset( &self, expression: &ResolvedExpression, store: &Arc<Store>, ) -> Result<Box<dyn Revset + '_>, RevsetEvaluationError> { let revset_impl = revset_engine::evaluate(expression, store, self)?; Ok(Box::new(revset_impl)) }} impl AsCompositeIndex for CompositeIndex { fn as_composite(&self) -> &CompositeIndex { self }} // In revset engine, we need to convert &CompositeIndex to &dyn Index.impl Index for &CompositeIndex { /// Suppose the given `commit_id` exists, returns the minimum prefix length /// to disambiguate it. The length to be returned is a number of hexadecimal /// digits. /// /// If the given `commit_id` doesn't exist, this will return the prefix /// length that never matches with any commit ids. fn shortest_unique_commit_id_prefix_len(&self, commit_id: &CommitId) -> usize { let (prev_id, next_id) = self.resolve_neighbor_commit_ids(commit_id); itertools::chain(prev_id, next_id) .map(|id| hex_util::common_hex_len(commit_id.as_bytes(), id.as_bytes()) + 1) .max() .unwrap_or(0) } fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> { self.ancestor_index_segments() .fold(PrefixResolution::NoMatch, |acc_match, segment| { if acc_match == PrefixResolution::AmbiguousMatch { acc_match // avoid checking the parent file(s) } else { let local_match = segment.resolve_commit_id_prefix(prefix); acc_match.plus(&local_match) } }) } fn has_id(&self, commit_id: &CommitId) -> bool { self.commit_id_to_pos(commit_id).is_some() } fn is_ancestor(&self, ancestor_id: &CommitId, descendant_id: &CommitId) -> bool { let ancestor_pos = self.commit_id_to_pos(ancestor_id).unwrap(); let descendant_pos = self.commit_id_to_pos(descendant_id).unwrap(); self.is_ancestor_pos(ancestor_pos, descendant_pos) } fn common_ancestors(&self, set1: &[CommitId], set2: &[CommitId]) -> Vec<CommitId> { let pos1 = set1 .iter() .map(|id| self.commit_id_to_pos(id).unwrap()) .collect_vec(); let pos2 = set2 .iter() .map(|id| self.commit_id_to_pos(id).unwrap()) .collect_vec(); self.common_ancestors_pos(pos1, pos2) .iter()Lines 1–500 of 671. Load more
// Copyright 2023 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![allow(missing_docs)] use std::cmp::max; use std::cmp::Ordering; use std::cmp::Reverse; use std::collections::binary_heap; use std::collections::BinaryHeap; use std::collections::HashSet; use std::iter; use std::mem; use std::sync::Arc; use std::sync::Mutex; use itertools::Itertools as _; use ref_cast::ref_cast_custom; use ref_cast::RefCastCustom; use super::entry::IndexEntry; use super::entry::IndexPosition; use super::entry::LocalPosition; use super::entry::SmallIndexPositionsVec; use super::entry::SmallLocalPositionsVec; use super::readonly::ReadonlyIndexSegment; use super::rev_walk::AncestorsBitSet; use super::revset_engine; use crate::backend::ChangeId; use crate::backend::CommitId; use crate::hex_util; use crate::index::AllHeadsForGcUnsupported; use crate::index::ChangeIdIndex; use crate::index::Index; use crate::index::IndexError; use crate::object_id::HexPrefix; use crate::object_id::ObjectId as _; use crate::object_id::PrefixResolution; use crate::revset::ResolvedExpression; use crate::revset::Revset; use crate::revset::RevsetEvaluationError; use crate::store::Store; pub(super) trait IndexSegment: Send + Sync { fn num_parent_commits(&self) -> u32; fn num_local_commits(&self) -> u32; fn parent_file(&self) -> Option<&Arc<ReadonlyIndexSegment>>; fn name(&self) -> Option<String>; fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<LocalPosition>; /// Suppose the given `commit_id` exists, returns the previous and next /// commit ids in lexicographical order. fn resolve_neighbor_commit_ids( &self, commit_id: &CommitId, ) -> (Option<CommitId>, Option<CommitId>); fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId>; fn resolve_neighbor_change_ids( &self, change_id: &ChangeId, ) -> (Option<ChangeId>, Option<ChangeId>); fn resolve_change_id_prefix( &self, prefix: &HexPrefix, ) -> PrefixResolution<(ChangeId, SmallLocalPositionsVec)>; fn generation_number(&self, local_pos: LocalPosition) -> u32; fn commit_id(&self, local_pos: LocalPosition) -> CommitId; fn change_id(&self, local_pos: LocalPosition) -> ChangeId; fn num_parents(&self, local_pos: LocalPosition) -> u32; fn parent_positions(&self, local_pos: LocalPosition) -> SmallIndexPositionsVec; } pub(super) type DynIndexSegment = dyn IndexSegment; /// Abstraction over owned and borrowed types that can be cheaply converted to /// a `CompositeIndex` reference. pub trait AsCompositeIndex { /// Returns reference wrapper that provides global access to this index. fn as_composite(&self) -> &CompositeIndex; } impl<T: AsCompositeIndex + ?Sized> AsCompositeIndex for &T { fn as_composite(&self) -> &CompositeIndex { <T as AsCompositeIndex>::as_composite(self) } } impl<T: AsCompositeIndex + ?Sized> AsCompositeIndex for &mut T { fn as_composite(&self) -> &CompositeIndex { <T as AsCompositeIndex>::as_composite(self) } } /// `CompositeIndex` provides an index of both commit IDs and change IDs. /// /// We refer to this as a composite index because it's a composite of multiple /// nested index segments where each parent segment is roughly twice as large /// its child. segment. This provides a good balance between read and write /// performance. // Reference wrapper that provides global access to nested index segments. #[derive(RefCastCustom)] #[repr(transparent)] pub struct CompositeIndex(DynIndexSegment); impl CompositeIndex { #[ref_cast_custom] pub(super) const fn new(segment: &DynIndexSegment) -> &Self; /// Iterates parent and its ancestor readonly index segments. pub(super) fn ancestor_files_without_local( &self, ) -> impl Iterator<Item = &Arc<ReadonlyIndexSegment>> { let parent_file = self.0.parent_file(); iter::successors(parent_file, |file| file.parent_file()) } /// Iterates self and its ancestor index segments. pub(super) fn ancestor_index_segments(&self) -> impl Iterator<Item = &DynIndexSegment> { iter::once(&self.0).chain( self.ancestor_files_without_local() .map(|file| file.as_ref() as &DynIndexSegment), ) } pub fn num_commits(&self) -> u32 { self.0.num_parent_commits() + self.0.num_local_commits() } pub fn stats(&self) -> IndexStats { let num_commits = self.num_commits(); let mut num_merges = 0; let mut max_generation_number = 0; let mut change_ids = HashSet::new(); for pos in 0..num_commits { let entry = self.entry_by_pos(IndexPosition(pos)); max_generation_number = max(max_generation_number, entry.generation_number()); if entry.num_parents() > 1 { num_merges += 1; } change_ids.insert(entry.change_id()); } let num_heads = u32::try_from(self.all_heads_pos().count()).unwrap(); let mut levels = self .ancestor_index_segments() .map(|segment| IndexLevelStats { num_commits: segment.num_local_commits(), name: segment.name(), }) .collect_vec(); levels.reverse(); IndexStats { num_commits, num_merges, max_generation_number, num_heads, num_changes: change_ids.len().try_into().unwrap(), levels, } } pub fn entry_by_pos(&self, pos: IndexPosition) -> IndexEntry<'_> { self.ancestor_index_segments() .find_map(|segment| { u32::checked_sub(pos.0, segment.num_parent_commits()) .map(|local_pos| IndexEntry::new(segment, pos, LocalPosition(local_pos))) }) .unwrap() } pub fn entry_by_id(&self, commit_id: &CommitId) -> Option<IndexEntry<'_>> { self.ancestor_index_segments().find_map(|segment| { let local_pos = segment.commit_id_to_pos(commit_id)?; let pos = IndexPosition(local_pos.0 + segment.num_parent_commits()); Some(IndexEntry::new(segment, pos, local_pos)) }) } pub fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<IndexPosition> { self.ancestor_index_segments().find_map(|segment| { let LocalPosition(local_pos) = segment.commit_id_to_pos(commit_id)?; Some(IndexPosition(local_pos + segment.num_parent_commits())) }) } /// Suppose the given `commit_id` exists, returns the previous and next /// commit ids in lexicographical order. pub(super) fn resolve_neighbor_commit_ids( &self, commit_id: &CommitId, ) -> (Option<CommitId>, Option<CommitId>) { self.ancestor_index_segments() .map(|segment| segment.resolve_neighbor_commit_ids(commit_id)) .reduce(|(acc_prev_id, acc_next_id), (prev_id, next_id)| { ( acc_prev_id.into_iter().chain(prev_id).max(), acc_next_id.into_iter().chain(next_id).min(), ) }) .unwrap() } /// Suppose the given `change_id` exists, returns the minimum prefix length /// to disambiguate it within all the indexed ids including hidden ones. pub(super) fn shortest_unique_change_id_prefix_len(&self, change_id: &ChangeId) -> usize { let (prev_id, next_id) = self.resolve_neighbor_change_ids(change_id); itertools::chain(prev_id, next_id) .map(|id| hex_util::common_hex_len(change_id.as_bytes(), id.as_bytes()) + 1) .max() .unwrap_or(0) } /// Suppose the given `change_id` exists, returns the previous and next /// change ids in lexicographical order. The returned change ids may be /// hidden. pub(super) fn resolve_neighbor_change_ids( &self, change_id: &ChangeId, ) -> (Option<ChangeId>, Option<ChangeId>) { self.ancestor_index_segments() .map(|segment| segment.resolve_neighbor_change_ids(change_id)) .reduce(|(acc_prev_id, acc_next_id), (prev_id, next_id)| { ( acc_prev_id.into_iter().chain(prev_id).max(), acc_next_id.into_iter().chain(next_id).min(), ) }) .unwrap() } /// Resolves the given change id `prefix` to the associated entries. The /// returned entries may be hidden. /// /// The returned index positions are sorted in ascending order. pub(super) fn resolve_change_id_prefix( &self, prefix: &HexPrefix, ) -> PrefixResolution<(ChangeId, SmallIndexPositionsVec)> { use PrefixResolution::*; self.ancestor_index_segments() .fold(NoMatch, |acc_match, segment| { if acc_match == AmbiguousMatch { return acc_match; // avoid checking the parent file(s) } let to_global_pos = { let num_parent_commits = segment.num_parent_commits(); move |LocalPosition(pos)| IndexPosition(pos + num_parent_commits) }; // Similar to PrefixResolution::plus(), but merges matches of the same id. match (acc_match, segment.resolve_change_id_prefix(prefix)) { (NoMatch, local_match) => local_match.map(|(id, positions)| { (id, positions.into_iter().map(to_global_pos).collect()) }), (acc_match, NoMatch) => acc_match, (AmbiguousMatch, _) => AmbiguousMatch, (_, AmbiguousMatch) => AmbiguousMatch, (SingleMatch((id1, _)), SingleMatch((id2, _))) if id1 != id2 => AmbiguousMatch, (SingleMatch((id, mut acc_positions)), SingleMatch((_, local_positions))) => { acc_positions .insert_many(0, local_positions.into_iter().map(to_global_pos)); SingleMatch((id, acc_positions)) } } }) } pub(super) fn is_ancestor_pos( &self, ancestor_pos: IndexPosition, descendant_pos: IndexPosition, ) -> bool { let ancestor_generation = self.entry_by_pos(ancestor_pos).generation_number(); let mut work = vec![descendant_pos]; let mut visited = HashSet::new(); while let Some(descendant_pos) = work.pop() { let descendant_entry = self.entry_by_pos(descendant_pos); if descendant_pos == ancestor_pos { return true; } if !visited.insert(descendant_entry.position()) { continue; } if descendant_entry.generation_number() <= ancestor_generation { continue; } work.extend(descendant_entry.parent_positions()); } false } /// Computes the greatest common ancestors. /// /// The returned index positions are sorted in descending order. pub(super) fn common_ancestors_pos( &self, set1: Vec<IndexPosition>, set2: Vec<IndexPosition>, ) -> Vec<IndexPosition> { let mut items1 = BinaryHeap::from(set1); let mut items2 = BinaryHeap::from(set2); let mut result = Vec::new(); while let (Some(&pos1), Some(&pos2)) = (items1.peek(), items2.peek()) { match pos1.cmp(&pos2) { Ordering::Greater => shift_to_parents(&mut items1, &self.entry_by_pos(pos1)), Ordering::Less => shift_to_parents(&mut items2, &self.entry_by_pos(pos2)), Ordering::Equal => { result.push(pos1); dedup_pop(&mut items1).unwrap(); dedup_pop(&mut items2).unwrap(); } } } self.heads_pos(result) } pub(super) fn all_heads(&self) -> impl Iterator<Item = CommitId> + use<'_> { self.all_heads_pos() .map(move |pos| self.entry_by_pos(pos).commit_id()) } pub(super) fn all_heads_pos(&self) -> impl Iterator<Item = IndexPosition> + use<> { // TODO: can be optimized to use bit vec and leading/trailing_ones() let num_commits = self.num_commits(); let mut not_head: Vec<bool> = vec![false; num_commits as usize]; for pos in 0..num_commits { let entry = self.entry_by_pos(IndexPosition(pos)); for IndexPosition(parent_pos) in entry.parent_positions() { not_head[parent_pos as usize] = true; } } not_head .into_iter() .enumerate() .filter(|&(_, b)| !b) .map(|(i, _)| IndexPosition(u32::try_from(i).unwrap())) } /// Returns the subset of positions in `candidate_positions` which refer to /// entries that are heads in the repository. /// /// The `candidate_positions` must be sorted in descending order, and have /// no duplicates. The returned head positions are also sorted in descending /// order. pub fn heads_pos(&self, candidate_positions: Vec<IndexPosition>) -> Vec<IndexPosition> { debug_assert!(candidate_positions.is_sorted_by(|a, b| a > b)); let Some(min_generation) = candidate_positions .iter() .map(|&pos| self.entry_by_pos(pos).generation_number()) .min() else { return candidate_positions; }; // Iterate though the candidates by reverse index position, keeping track of the // ancestors of already-found heads. If a candidate is an ancestor of an // already-found head, then it can be removed. let mut parents = BinaryHeap::new(); let mut heads = Vec::new(); 'outer: for candidate in candidate_positions { while let Some(&parent) = parents.peek().filter(|&&parent| parent >= candidate) { let entry = self.entry_by_pos(parent); if entry.generation_number() <= min_generation { dedup_pop(&mut parents).unwrap(); } else { shift_to_parents(&mut parents, &entry); } if parent == candidate { // The candidate is an ancestor of an existing head, so we can skip it. continue 'outer; } } // No parents matched, so this commit is a head. let entry = self.entry_by_pos(candidate); parents.extend(entry.parent_positions()); heads.push(candidate); } heads } /// Find the heads of a range of positions `roots..heads`, applying a filter /// to the commits in the range. The heads are sorted in descending order. /// The filter will also be called in descending index position order. pub fn heads_from_range_and_filter<E>( &self, roots: Vec<IndexPosition>, heads: Vec<IndexPosition>, mut filter: impl FnMut(IndexPosition) -> Result<bool, E>, ) -> Result<Vec<IndexPosition>, E> { if heads.is_empty() { return Ok(heads); } let mut wanted_queue = BinaryHeap::from(heads); let mut unwanted_queue = BinaryHeap::from(roots); let mut found_heads = Vec::new(); while let Some(&pos) = wanted_queue.peek() { if shift_to_parents_until(&mut unwanted_queue, self, pos) { dedup_pop(&mut wanted_queue); continue; } let entry = self.entry_by_pos(pos); if filter(pos)? { dedup_pop(&mut wanted_queue); unwanted_queue.extend(entry.parent_positions()); found_heads.push(pos); } else { shift_to_parents(&mut wanted_queue, &entry); } } Ok(found_heads) } pub(super) fn evaluate_revset( &self, expression: &ResolvedExpression, store: &Arc<Store>, ) -> Result<Box<dyn Revset + '_>, RevsetEvaluationError> { let revset_impl = revset_engine::evaluate(expression, store, self)?; Ok(Box::new(revset_impl)) } } impl AsCompositeIndex for CompositeIndex { fn as_composite(&self) -> &CompositeIndex { self } } // In revset engine, we need to convert &CompositeIndex to &dyn Index. impl Index for &CompositeIndex { /// Suppose the given `commit_id` exists, returns the minimum prefix length /// to disambiguate it. The length to be returned is a number of hexadecimal /// digits. /// /// If the given `commit_id` doesn't exist, this will return the prefix /// length that never matches with any commit ids. fn shortest_unique_commit_id_prefix_len(&self, commit_id: &CommitId) -> usize { let (prev_id, next_id) = self.resolve_neighbor_commit_ids(commit_id); itertools::chain(prev_id, next_id) .map(|id| hex_util::common_hex_len(commit_id.as_bytes(), id.as_bytes()) + 1) .max() .unwrap_or(0) } fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> { self.ancestor_index_segments() .fold(PrefixResolution::NoMatch, |acc_match, segment| { if acc_match == PrefixResolution::AmbiguousMatch { acc_match // avoid checking the parent file(s) } else { let local_match = segment.resolve_commit_id_prefix(prefix); acc_match.plus(&local_match) } }) } fn has_id(&self, commit_id: &CommitId) -> bool { self.commit_id_to_pos(commit_id).is_some() } fn is_ancestor(&self, ancestor_id: &CommitId, descendant_id: &CommitId) -> bool { let ancestor_pos = self.commit_id_to_pos(ancestor_id).unwrap(); let descendant_pos = self.commit_id_to_pos(descendant_id).unwrap(); self.is_ancestor_pos(ancestor_pos, descendant_pos) } fn common_ancestors(&self, set1: &[CommitId], set2: &[CommitId]) -> Vec<CommitId> { let pos1 = set1 .iter() .map(|id| self.commit_id_to_pos(id).unwrap()) .collect_vec(); let pos2 = set2 .iter() .map(|id| self.commit_id_to_pos(id).unwrap()) .collect_vec(); self.common_ancestors_pos(pos1, pos2) .iter()