#![allow(missing_docs)]
use std::iter;
use std::marker::PhantomData;
use std::rc::Rc;
use std::sync::Arc;
use itertools::Itertools as _;
use once_cell::unsync::OnceCell;
use thiserror::Error;
use crate::backend::ChangeId;
use crate::backend::CommitId;
use crate::hex_util;
use crate::object_id::HexPrefix;
use crate::object_id::ObjectId;
use crate::object_id::PrefixResolution;
use crate::repo::Repo;
use crate::revset::DefaultSymbolResolver;
use crate::revset::RevsetEvaluationError;
use crate::revset::RevsetExtensions;
use crate::revset::RevsetResolutionError;
use crate::revset::SymbolResolverExtension;
use crate::revset::UserRevsetExpression;
#[derive(Debug, Error)]
pub enum IdPrefixIndexLoadError {
#[error("Failed to resolve short-prefixes disambiguation revset")]
Resolution(#[from] RevsetResolutionError),
#[error("Failed to evaluate short-prefixes disambiguation revset")]
Evaluation(#[from] RevsetEvaluationError),
}
struct DisambiguationData {
expression: Rc<UserRevsetExpression>,
indexes: OnceCell<Indexes>,
}
struct Indexes {
commit_change_ids: Vec<(CommitId, ChangeId)>,
commit_index: IdIndex<CommitId, u32, 4>,
change_index: IdIndex<ChangeId, u32, 4>,
}
impl DisambiguationData {
fn indexes(
&self,
repo: &dyn Repo,
extensions: &[impl AsRef<dyn SymbolResolverExtension>],
) -> Result<&Indexes, IdPrefixIndexLoadError> {
self.indexes.get_or_try_init(|| {
let symbol_resolver = DefaultSymbolResolver::new(repo, extensions);
let revset = self
.expression
.resolve_user_expression(repo, &symbol_resolver)?
.evaluate(repo)?;
let commit_change_ids: Vec<_> = revset.commit_change_ids().try_collect()?;
let mut commit_index = IdIndex::with_capacity(commit_change_ids.len());
let mut change_index = IdIndex::with_capacity(commit_change_ids.len());
for (i, (commit_id, change_id)) in commit_change_ids.iter().enumerate() {
let i: u32 = i.try_into().unwrap();
commit_index.insert(commit_id, i);
change_index.insert(change_id, i);
}
Ok(Indexes {
commit_change_ids,
commit_index: commit_index.build(),
change_index: change_index.build(),
})
})
}
}
impl<'a> IdIndexSource<u32> for &'a [(CommitId, ChangeId)] {
type Entry = &'a (CommitId, ChangeId);
fn entry_at(&self, pointer: &u32) -> Self::Entry {
&self[*pointer as usize]
}
}
impl IdIndexSourceEntry<CommitId> for &'_ (CommitId, ChangeId) {
fn to_key(&self) -> CommitId {
let (commit_id, _) = self;
commit_id.clone()
}
}
impl IdIndexSourceEntry<ChangeId> for &'_ (CommitId, ChangeId) {
fn to_key(&self) -> ChangeId {
let (_, change_id) = self;
change_id.clone()
}
}
#[derive(Default)]
pub struct IdPrefixContext {
disambiguation: Option<DisambiguationData>,
extensions: Arc<RevsetExtensions>,
}
impl IdPrefixContext {
pub fn new(extensions: Arc<RevsetExtensions>) -> Self {
Self {
disambiguation: None,
extensions,
}
}
pub fn disambiguate_within(mut self, expression: Rc<UserRevsetExpression>) -> Self {
self.disambiguation = Some(DisambiguationData {
expression,
indexes: OnceCell::new(),
});
self
}
pub fn populate(&self, repo: &dyn Repo) -> Result<IdPrefixIndex<'_>, IdPrefixIndexLoadError> {
let indexes = if let Some(disambiguation) = &self.disambiguation {
Some(disambiguation.indexes(repo, self.extensions.symbol_resolvers())?)
} else {
None
};
Ok(IdPrefixIndex { indexes })
}
}
pub struct IdPrefixIndex<'a> {
indexes: Option<&'a Indexes>,
}
impl IdPrefixIndex<'_> {
pub const fn empty() -> IdPrefixIndex<'static> {
IdPrefixIndex { indexes: None }
}
pub fn resolve_commit_prefix(
&self,
repo: &dyn Repo,
prefix: &HexPrefix,
) -> PrefixResolution<CommitId> {
if let Some(indexes) = self.indexes {
let resolution = indexes
.commit_index
.resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
if let PrefixResolution::SingleMatch(id) = resolution {
if repo.index().has_id(&id) {
return PrefixResolution::SingleMatch(id);
} else {
return PrefixResolution::NoMatch;
}
}
}
repo.index().resolve_commit_id_prefix(prefix)
}
pub fn shortest_commit_prefix_len(&self, repo: &dyn Repo, commit_id: &CommitId) -> usize {
if let Some(indexes) = self.indexes {
if let Some(lookup) = indexes
.commit_index
.lookup_exact(&*indexes.commit_change_ids, commit_id)
{
return lookup.shortest_unique_prefix_len();
}
}
repo.index().shortest_unique_commit_id_prefix_len(commit_id)
}
pub fn resolve_change_prefix(
&self,
repo: &dyn Repo,
prefix: &HexPrefix,
) -> PrefixResolution<Vec<CommitId>> {
if let Some(indexes) = self.indexes {
let resolution = indexes
.change_index
.resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
if let PrefixResolution::SingleMatch(change_id) = resolution {
return match repo.resolve_change_id(&change_id) {
Some(commit_ids) => PrefixResolution::SingleMatch(commit_ids),
None => PrefixResolution::NoMatch,
};
}
}
repo.resolve_change_id_prefix(prefix)
}
pub fn shortest_change_prefix_len(&self, repo: &dyn Repo, change_id: &ChangeId) -> usize {
if let Some(indexes) = self.indexes {
if let Some(lookup) = indexes
.change_index
.lookup_exact(&*indexes.commit_change_ids, change_id)
{
return lookup.shortest_unique_prefix_len();
}
}
repo.shortest_unique_change_id_prefix_len(change_id)
}
}
#[derive(Clone, Debug)]
pub struct IdIndex<K, P, const N: usize> {
index: Vec<([u8; N], P)>,
phantom_key: PhantomData<K>,
}
pub trait IdIndexSource<P> {
type Entry;
fn entry_at(&self, pointer: &P) -> Self::Entry;
}
pub trait IdIndexSourceEntry<K> {
fn to_key(&self) -> K;
}
#[derive(Clone, Debug)]
pub struct IdIndexBuilder<K, P, const N: usize> {
unsorted_index: Vec<([u8; N], P)>,
phantom_key: PhantomData<K>,
}
impl<K, P, const N: usize> IdIndexBuilder<K, P, N>
where
K: ObjectId + Ord,
{
pub fn insert(&mut self, key: &K, pointer: P) {
let short_key = unwrap_as_short_key(key.as_bytes());
self.unsorted_index.push((*short_key, pointer));
}
pub fn build(self) -> IdIndex<K, P, N> {
let mut index = self.unsorted_index;
index.sort_unstable_by_key(|(s, _)| *s);
let phantom_key = self.phantom_key;
IdIndex { index, phantom_key }
}
}
impl<K, P, const N: usize> IdIndex<K, P, N>
where
K: ObjectId + Ord,
{
pub fn builder() -> IdIndexBuilder<K, P, N> {
IdIndexBuilder {
unsorted_index: Vec::new(),
phantom_key: PhantomData,
}
}
pub fn with_capacity(capacity: usize) -> IdIndexBuilder<K, P, N> {
IdIndexBuilder {
unsorted_index: Vec::with_capacity(capacity),
phantom_key: PhantomData,
}
}
pub fn resolve_prefix_with<B, S, U>(
&self,
source: S,
prefix: &HexPrefix,
entry_mapper: impl FnMut(S::Entry) -> U,
) -> PrefixResolution<(K, B)>
where
B: FromIterator<U>,
S: IdIndexSource<P>,
S::Entry: IdIndexSourceEntry<K>,
{
fn collect<B, K, E, U>(
mut range: impl Iterator<Item = (K, E)>,
mut entry_mapper: impl FnMut(E) -> U,
) -> PrefixResolution<(K, B)>
where
B: FromIterator<U>,
K: Eq,
{
if let Some((first_key, first_entry)) = range.next() {
let maybe_values: Option<B> = iter::once(Some(entry_mapper(first_entry)))
.chain(range.map(|(k, e)| (k == first_key).then(|| entry_mapper(e))))
.collect();
if let Some(values) = maybe_values {
PrefixResolution::SingleMatch((first_key, values))
} else {
PrefixResolution::AmbiguousMatch
}
} else {
PrefixResolution::NoMatch
}
}
let min_bytes = prefix.min_prefix_bytes();
if min_bytes.is_empty() {
return PrefixResolution::AmbiguousMatch;
}
let to_key_entry_pair = |(_, pointer): &(_, P)| -> (K, S::Entry) {
let entry = source.entry_at(pointer);
(entry.to_key(), entry)
};
if min_bytes.len() > N {
let short_bytes = unwrap_as_short_key(min_bytes);
let pos = self.index.partition_point(|(s, _)| s < short_bytes);
let range = self.index[pos..]
.iter()
.take_while(|(s, _)| s == short_bytes)
.map(to_key_entry_pair)
.filter(|(k, _)| prefix.matches(k));
collect(range, entry_mapper)
} else {
let pos = self.index.partition_point(|(s, _)| &s[..] < min_bytes);
let range = self.index[pos..]
.iter()
.map(to_key_entry_pair)
.take_while(|(k, _)| prefix.matches(k));
collect(range, entry_mapper)
}
}
pub fn resolve_prefix_to_key<S>(&self, source: S, prefix: &HexPrefix) -> PrefixResolution<K>
where
S: IdIndexSource<P>,
S::Entry: IdIndexSourceEntry<K>,
{
self.resolve_prefix_with(source, prefix, |_| ())
.map(|(key, ())| key)
}
pub fn lookup_exact<'i, 'q, S>(
&'i self,
source: S,
key: &'q K,
) -> Option<IdIndexLookup<'i, 'q, K, P, S, N>>
where
S: IdIndexSource<P>,
S::Entry: IdIndexSourceEntry<K>,
{
let lookup = self.lookup_some(source, key);
lookup.has_key().then_some(lookup)
}
fn lookup_some<'i, 'q, S>(&'i self, source: S, key: &'q K) -> IdIndexLookup<'i, 'q, K, P, S, N>
where
S: IdIndexSource<P>,
{
let short_key = unwrap_as_short_key(key.as_bytes());
let index = &self.index;
let pos = index.partition_point(|(s, _)| s < short_key);
IdIndexLookup {
index,
source,
key,
pos,
}
}
pub fn shortest_unique_prefix_len<S>(&self, source: S, key: &K) -> usize
where
S: IdIndexSource<P>,
S::Entry: IdIndexSourceEntry<K>,
{
self.lookup_some(source, key).shortest_unique_prefix_len()
}
}
#[derive(Clone, Copy, Debug)]
pub struct IdIndexLookup<'i, 'q, K, P, S, const N: usize> {
index: &'i Vec<([u8; N], P)>,
source: S,
key: &'q K,
pos: usize, }
impl<K, P, S, const N: usize> IdIndexLookup<'_, '_, K, P, S, N>
where
K: ObjectId + Eq,
S: IdIndexSource<P>,
S::Entry: IdIndexSourceEntry<K>,
{
fn has_key(&self) -> bool {
let short_key = unwrap_as_short_key(self.key.as_bytes());
self.index[self.pos..]
.iter()
.take_while(|(s, _)| s == short_key)
.any(|(_, p)| self.source.entry_at(p).to_key() == *self.key)
}
pub fn shortest_unique_prefix_len(&self) -> usize {
let short_key = unwrap_as_short_key(self.key.as_bytes());
let left = self.pos.checked_sub(1).map(|p| &self.index[p]);
let (current, right) = {
let range = &self.index[self.pos..];
let count = range.iter().take_while(|(s, _)| s == short_key).count();
(&range[..count], range.get(count))
};
let unique_len = |a: &[u8], b: &[u8]| hex_util::common_hex_len(a, b) + 1;
let neighbor_lens = left
.iter()
.chain(&right)
.map(|(s, _)| unique_len(s, short_key));
let current_lens = current
.iter()
.map(|(_, p)| self.source.entry_at(p).to_key())
.filter(|key| key != self.key)
.map(|key| unique_len(key.as_bytes(), self.key.as_bytes()));
neighbor_lens.chain(current_lens).max().unwrap_or(1)
}
}
fn unwrap_as_short_key<const N: usize>(key_bytes: &[u8]) -> &[u8; N] {
let short_slice = key_bytes.get(..N).expect("key too short");
short_slice.try_into().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, Eq, PartialEq)]
struct Position(usize);
impl<'a, V> IdIndexSource<Position> for &'a [(ChangeId, V)] {
type Entry = &'a (ChangeId, V);
fn entry_at(&self, pointer: &Position) -> Self::Entry {
&self[pointer.0]
}
}
impl<V> IdIndexSourceEntry<ChangeId> for &'_ (ChangeId, V) {
fn to_key(&self) -> ChangeId {
let (change_id, _) = self;
change_id.clone()
}
}
fn build_id_index<V, const N: usize>(
entries: &[(ChangeId, V)],
) -> IdIndex<ChangeId, Position, N> {
let mut builder = IdIndex::with_capacity(entries.len());
for (i, (k, _)) in entries.iter().enumerate() {
builder.insert(k, Position(i));
}
builder.build()
}
#[test]
fn test_id_index_resolve_prefix() {
let source = vec![
(ChangeId::from_hex("0000"), 0),
(ChangeId::from_hex("0099"), 1),
(ChangeId::from_hex("0099"), 2),
(ChangeId::from_hex("0aaa"), 3),
(ChangeId::from_hex("0aab"), 4),
];
let id_index = build_id_index::<_, 2>(&source);
let resolve_prefix = |prefix: &HexPrefix| {
let resolution: PrefixResolution<(_, Vec<_>)> =
id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
resolution.map(|(key, mut values)| {
values.sort(); (key, values)
})
};
assert_eq!(
resolve_prefix(&HexPrefix::new("0").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("00").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("000").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0001").unwrap()),
PrefixResolution::NoMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("009").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0aa").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0aab").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
);
assert_eq!(
resolve_prefix(&HexPrefix::new("f").unwrap()),
PrefixResolution::NoMatch,
);
let id_index = build_id_index::<_, 1>(&source);
let resolve_prefix = |prefix: &HexPrefix| {
let resolution: PrefixResolution<(_, Vec<_>)> =
id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
resolution.map(|(key, mut values)| {
values.sort(); (key, values)
})
};
assert_eq!(
resolve_prefix(&HexPrefix::new("00").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("000").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0001").unwrap()),
PrefixResolution::NoMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("009").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0a").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0aa").unwrap()),
PrefixResolution::AmbiguousMatch,
);
assert_eq!(
resolve_prefix(&HexPrefix::new("0aab").unwrap()),
PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
);
}
#[test]
fn test_lookup_exact() {
let source: Vec<(ChangeId, ())> = vec![];
let id_index = build_id_index::<_, 1>(&source);
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("00"))
.is_none());
let source = vec![
(ChangeId::from_hex("ab00"), ()),
(ChangeId::from_hex("ab01"), ()),
];
let id_index = build_id_index::<_, 1>(&source);
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("aa00"))
.is_none());
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("ab00"))
.is_some());
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("ab01"))
.is_some());
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("ab02"))
.is_none());
assert!(id_index
.lookup_exact(&*source, &ChangeId::from_hex("ac00"))
.is_none());
}
#[test]
fn test_id_index_shortest_unique_prefix_len() {
let source: Vec<(ChangeId, ())> = vec![];
let id_index = build_id_index::<_, 1>(&source);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("00")),
1
);
let source = vec![
(ChangeId::from_hex("ab"), ()),
(ChangeId::from_hex("acd0"), ()),
(ChangeId::from_hex("acd0"), ()), ];
let id_index = build_id_index::<_, 1>(&source);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
2
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ac")),
3
);
let source = vec![
(ChangeId::from_hex("ab"), ()),
(ChangeId::from_hex("acd0"), ()),
(ChangeId::from_hex("acf0"), ()),
(ChangeId::from_hex("a0"), ()),
(ChangeId::from_hex("ba"), ()),
];
let id_index = build_id_index::<_, 1>(&source);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("a0")),
2
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ba")),
1
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ab")),
2
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
3
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("c0")),
1
);
let source = vec![
(ChangeId::from_hex("000000"), ()),
(ChangeId::from_hex("01ffff"), ()),
(ChangeId::from_hex("010000"), ()),
(ChangeId::from_hex("01fffe"), ()),
(ChangeId::from_hex("ffffff"), ()),
];
let id_index = build_id_index::<_, 1>(&source);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01ffff")),
6
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("010000")),
3
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01fffe")),
6
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("000000")),
2
);
assert_eq!(
id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ffffff")),
1
);
}
}