Skip to main content

jj_lib/
id_prefix.rs

1// Copyright 2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![expect(missing_docs)]
16
17use std::iter;
18use std::marker::PhantomData;
19use std::sync::Arc;
20
21use futures::TryStreamExt as _;
22use once_cell::sync::OnceCell;
23use pollster::FutureExt as _;
24use thiserror::Error;
25
26use crate::backend::ChangeId;
27use crate::backend::CommitId;
28use crate::hex_util;
29use crate::index::IndexResult;
30use crate::index::ResolvedChangeTargets;
31use crate::object_id::HexPrefix;
32use crate::object_id::ObjectId;
33use crate::object_id::PrefixResolution;
34use crate::repo::Repo;
35use crate::revset::RevsetEvaluationError;
36use crate::revset::RevsetExtensions;
37use crate::revset::RevsetResolutionError;
38use crate::revset::SymbolResolver;
39use crate::revset::SymbolResolverExtension;
40use crate::revset::UserRevsetExpression;
41use crate::view::View;
42
43#[derive(Debug, Error)]
44pub enum IdPrefixIndexLoadError {
45    #[error("Failed to resolve short-prefixes disambiguation revset")]
46    Resolution(#[from] RevsetResolutionError),
47    #[error("Failed to evaluate short-prefixes disambiguation revset")]
48    Evaluation(#[from] RevsetEvaluationError),
49}
50
51struct DisambiguationData {
52    expression: Arc<UserRevsetExpression>,
53    indexes: OnceCell<Indexes>,
54}
55
56struct Indexes {
57    commit_change_ids: Vec<(CommitId, ChangeId)>,
58    commit_index: IdIndex<CommitId, u32, 4>,
59    change_index: IdIndex<ChangeId, u32, 4>,
60}
61
62impl DisambiguationData {
63    fn indexes(
64        &self,
65        repo: &dyn Repo,
66        extensions: &[Box<dyn SymbolResolverExtension>],
67    ) -> Result<&Indexes, IdPrefixIndexLoadError> {
68        self.indexes.get_or_try_init(|| {
69            let symbol_resolver = SymbolResolver::new(repo, extensions);
70            let revset = self
71                .expression
72                .resolve_user_expression(repo, &symbol_resolver)?
73                .evaluate(repo)?;
74
75            let commit_change_ids: Vec<_> = revset.commit_change_ids().try_collect().block_on()?;
76            let mut commit_index = IdIndex::with_capacity(commit_change_ids.len());
77            let mut change_index = IdIndex::with_capacity(commit_change_ids.len());
78            for (i, (commit_id, change_id)) in commit_change_ids.iter().enumerate() {
79                let i: u32 = i.try_into().unwrap();
80                commit_index.insert(commit_id, i);
81                change_index.insert(change_id, i);
82            }
83            Ok(Indexes {
84                commit_change_ids,
85                commit_index: commit_index.build(),
86                change_index: change_index.build(),
87            })
88        })
89    }
90}
91
92impl<'a> IdIndexSource<u32> for &'a [(CommitId, ChangeId)] {
93    type Entry = &'a (CommitId, ChangeId);
94
95    fn entry_at(&self, pointer: &u32) -> Self::Entry {
96        &self[*pointer as usize]
97    }
98}
99
100impl IdIndexSourceEntry<CommitId> for &'_ (CommitId, ChangeId) {
101    fn to_key(&self) -> CommitId {
102        let (commit_id, _) = self;
103        commit_id.clone()
104    }
105}
106
107impl IdIndexSourceEntry<ChangeId> for &'_ (CommitId, ChangeId) {
108    fn to_key(&self) -> ChangeId {
109        let (_, change_id) = self;
110        change_id.clone()
111    }
112}
113
114/// Manages configuration and cache of commit/change ID disambiguation index.
115#[derive(Default)]
116pub struct IdPrefixContext {
117    disambiguation: Option<DisambiguationData>,
118    extensions: Arc<RevsetExtensions>,
119}
120
121impl IdPrefixContext {
122    pub fn new(extensions: Arc<RevsetExtensions>) -> Self {
123        Self {
124            disambiguation: None,
125            extensions,
126        }
127    }
128
129    pub fn disambiguate_within(mut self, expression: Arc<UserRevsetExpression>) -> Self {
130        self.disambiguation = Some(DisambiguationData {
131            expression,
132            indexes: OnceCell::new(),
133        });
134        self
135    }
136
137    /// Loads disambiguation index once, returns a borrowed index to
138    /// disambiguate commit/change IDs.
139    pub fn populate(&self, repo: &dyn Repo) -> Result<IdPrefixIndex<'_>, IdPrefixIndexLoadError> {
140        let indexes = if let Some(disambiguation) = &self.disambiguation {
141            Some(disambiguation.indexes(repo, self.extensions.symbol_resolvers())?)
142        } else {
143            None
144        };
145        Ok(IdPrefixIndex { indexes })
146    }
147}
148
149/// Loaded index to disambiguate commit/change IDs.
150pub struct IdPrefixIndex<'a> {
151    indexes: Option<&'a Indexes>,
152}
153
154impl IdPrefixIndex<'_> {
155    /// Returns an empty index that just falls back to a provided `repo`.
156    pub const fn empty() -> IdPrefixIndex<'static> {
157        IdPrefixIndex { indexes: None }
158    }
159
160    /// Resolve an unambiguous commit ID prefix.
161    pub fn resolve_commit_prefix(
162        &self,
163        repo: &dyn Repo,
164        prefix: &HexPrefix,
165    ) -> IndexResult<PrefixResolution<CommitId>> {
166        if let Some(indexes) = self.indexes {
167            let resolution = indexes
168                .commit_index
169                .resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
170            match resolution {
171                PrefixResolution::NoMatch => {
172                    // Fall back to resolving in entire repo
173                }
174                PrefixResolution::SingleMatch(id) => {
175                    // The disambiguation set may be loaded from a different repo,
176                    // and contain a commit that doesn't exist in the current repo.
177                    if repo.index().has_id(&id)? {
178                        return Ok(PrefixResolution::SingleMatch(id));
179                    } else {
180                        return Ok(PrefixResolution::NoMatch);
181                    }
182                }
183                PrefixResolution::AmbiguousMatch => {
184                    return Ok(PrefixResolution::AmbiguousMatch);
185                }
186            }
187        }
188        repo.index().resolve_commit_id_prefix(prefix)
189    }
190
191    /// Returns the shortest length of a prefix of `commit_id` that can still be
192    /// resolved by `resolve_commit_prefix()` and [`SymbolResolver`].
193    pub fn shortest_commit_prefix_len(
194        &self,
195        repo: &dyn Repo,
196        commit_id: &CommitId,
197    ) -> IndexResult<usize> {
198        let len = self.shortest_commit_prefix_len_exact(repo, commit_id)?;
199        Ok(disambiguate_prefix_with_refs(
200            repo.view(),
201            &commit_id.to_string(),
202            len,
203        ))
204    }
205
206    pub fn shortest_commit_prefix_len_exact(
207        &self,
208        repo: &dyn Repo,
209        commit_id: &CommitId,
210    ) -> IndexResult<usize> {
211        if let Some(indexes) = self.indexes
212            && let Some(lookup) = indexes
213                .commit_index
214                .lookup_exact(&*indexes.commit_change_ids, commit_id)
215        {
216            return Ok(lookup.shortest_unique_prefix_len());
217        }
218        repo.index().shortest_unique_commit_id_prefix_len(commit_id)
219    }
220
221    /// Resolve an unambiguous change ID prefix to the commit IDs in the revset.
222    pub fn resolve_change_prefix(
223        &self,
224        repo: &dyn Repo,
225        prefix: &HexPrefix,
226    ) -> IndexResult<PrefixResolution<ResolvedChangeTargets>> {
227        if let Some(indexes) = self.indexes {
228            let resolution = indexes
229                .change_index
230                .resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
231            match resolution {
232                PrefixResolution::NoMatch => {
233                    // Fall back to resolving in entire repo
234                }
235                PrefixResolution::SingleMatch(change_id) => {
236                    return match repo.resolve_change_id(&change_id)? {
237                        // There may be more commits with this change id outside the narrower sets.
238                        Some(commit_ids) => Ok(PrefixResolution::SingleMatch(commit_ids)),
239                        // The disambiguation set may contain hidden commits.
240                        None => Ok(PrefixResolution::NoMatch),
241                    };
242                }
243                PrefixResolution::AmbiguousMatch => {
244                    return Ok(PrefixResolution::AmbiguousMatch);
245                }
246            }
247        }
248        repo.resolve_change_id_prefix(prefix)
249    }
250
251    /// Returns the shortest length of a prefix of `change_id` that can still be
252    /// resolved by `resolve_change_prefix()` and [`SymbolResolver`].
253    pub fn shortest_change_prefix_len(
254        &self,
255        repo: &dyn Repo,
256        change_id: &ChangeId,
257    ) -> IndexResult<usize> {
258        let len = self.shortest_change_prefix_len_exact(repo, change_id)?;
259        Ok(disambiguate_prefix_with_refs(
260            repo.view(),
261            &change_id.to_string(),
262            len,
263        ))
264    }
265
266    fn shortest_change_prefix_len_exact(
267        &self,
268        repo: &dyn Repo,
269        change_id: &ChangeId,
270    ) -> IndexResult<usize> {
271        if let Some(indexes) = self.indexes
272            && let Some(lookup) = indexes
273                .change_index
274                .lookup_exact(&*indexes.commit_change_ids, change_id)
275        {
276            return Ok(lookup.shortest_unique_prefix_len());
277        }
278        repo.shortest_unique_change_id_prefix_len(change_id)
279    }
280}
281
282fn disambiguate_prefix_with_refs(view: &View, id_sym: &str, min_len: usize) -> usize {
283    debug_assert!(id_sym.is_ascii());
284    (min_len..id_sym.len())
285        .find(|&n| {
286            // Tags, bookmarks, and Git refs have higher priority, but Git refs
287            // should include "/" char. Extension symbols have lower priority.
288            let prefix = &id_sym[..n];
289            view.get_local_tag(prefix.as_ref()).is_absent()
290                && view.get_local_bookmark(prefix.as_ref()).is_absent()
291        })
292        // No need to test conflicts with the full ID. We have to return some
293        // valid length anyway.
294        .unwrap_or(id_sym.len())
295}
296
297/// In-memory immutable index to do prefix lookup of key `K` through `P`.
298///
299/// In a nutshell, this is a mapping of `K` -> `P` -> `S::Entry` where `S:
300/// IdIndexSource<P>`. The source table `S` isn't owned by this index.
301///
302/// This index stores first `N` bytes of each key `K` associated with the
303/// pointer `P`. `K` may be a heap-allocated object. `P` is supposed to be
304/// a cheap value type like `u32` or `usize`. As the index entry of type
305/// `([u8; N], P)` is small and has no indirect reference, constructing
306/// the index should be faster than sorting the source `(K, _)` pairs.
307///
308/// A key `K` must be at least `N` bytes long.
309#[derive(Clone, Debug)]
310pub struct IdIndex<K, P, const N: usize> {
311    // Maybe better to build separate (keys, values) vectors, but there's no std function
312    // to co-sort them.
313    index: Vec<([u8; N], P)>,
314    // Let's pretend [u8; N] above were of type K. It helps type inference, and ensures that
315    // IdIndexSource has the same key type.
316    phantom_key: PhantomData<K>,
317}
318
319/// Source table for `IdIndex` to map pointer of type `P` to entry.
320pub trait IdIndexSource<P> {
321    type Entry;
322
323    fn entry_at(&self, pointer: &P) -> Self::Entry;
324}
325
326/// Source table entry of `IdIndex`, which is conceptually a `(key, value)`
327/// pair.
328pub trait IdIndexSourceEntry<K> {
329    fn to_key(&self) -> K;
330}
331
332#[derive(Clone, Debug)]
333pub struct IdIndexBuilder<K, P, const N: usize> {
334    unsorted_index: Vec<([u8; N], P)>,
335    phantom_key: PhantomData<K>,
336}
337
338impl<K, P, const N: usize> IdIndexBuilder<K, P, N>
339where
340    K: ObjectId + Ord,
341{
342    /// Inserts new entry. Multiple values can be associated with a single key.
343    pub fn insert(&mut self, key: &K, pointer: P) {
344        let short_key = unwrap_as_short_key(key.as_bytes());
345        self.unsorted_index.push((*short_key, pointer));
346    }
347
348    pub fn build(self) -> IdIndex<K, P, N> {
349        let mut index = self.unsorted_index;
350        index.sort_unstable_by_key(|(s, _)| *s);
351        let phantom_key = self.phantom_key;
352        IdIndex { index, phantom_key }
353    }
354}
355
356impl<K, P, const N: usize> IdIndex<K, P, N>
357where
358    K: ObjectId + Ord,
359{
360    pub fn builder() -> IdIndexBuilder<K, P, N> {
361        IdIndexBuilder {
362            unsorted_index: Vec::new(),
363            phantom_key: PhantomData,
364        }
365    }
366
367    pub fn with_capacity(capacity: usize) -> IdIndexBuilder<K, P, N> {
368        IdIndexBuilder {
369            unsorted_index: Vec::with_capacity(capacity),
370            phantom_key: PhantomData,
371        }
372    }
373
374    /// Looks up entries with the given prefix, and collects values if matched
375    /// entries have unambiguous keys.
376    pub fn resolve_prefix_with<B, S, U>(
377        &self,
378        source: S,
379        prefix: &HexPrefix,
380        entry_mapper: impl FnMut(S::Entry) -> U,
381    ) -> PrefixResolution<(K, B)>
382    where
383        B: FromIterator<U>,
384        S: IdIndexSource<P>,
385        S::Entry: IdIndexSourceEntry<K>,
386    {
387        fn collect<B, K, E, U>(
388            mut range: impl Iterator<Item = (K, E)>,
389            mut entry_mapper: impl FnMut(E) -> U,
390        ) -> PrefixResolution<(K, B)>
391        where
392            B: FromIterator<U>,
393            K: Eq,
394        {
395            if let Some((first_key, first_entry)) = range.next() {
396                let maybe_values: Option<B> = iter::once(Some(entry_mapper(first_entry)))
397                    .chain(range.map(|(k, e)| (k == first_key).then(|| entry_mapper(e))))
398                    .collect();
399                if let Some(values) = maybe_values {
400                    PrefixResolution::SingleMatch((first_key, values))
401                } else {
402                    PrefixResolution::AmbiguousMatch
403                }
404            } else {
405                PrefixResolution::NoMatch
406            }
407        }
408
409        let min_bytes = prefix.min_prefix_bytes();
410        if min_bytes.is_empty() {
411            // We consider an empty prefix ambiguous even if the index has a single entry.
412            return PrefixResolution::AmbiguousMatch;
413        }
414
415        let to_key_entry_pair = |(_, pointer): &(_, P)| -> (K, S::Entry) {
416            let entry = source.entry_at(pointer);
417            (entry.to_key(), entry)
418        };
419        if min_bytes.len() > N {
420            // If the min prefix (including odd byte) is longer than the stored short keys,
421            // we are sure that min_bytes[..N] does not include the odd byte. Use it to
422            // take contiguous range, then filter by (longer) prefix.matches().
423            let short_bytes = unwrap_as_short_key(min_bytes);
424            let pos = self.index.partition_point(|(s, _)| s < short_bytes);
425            let range = self.index[pos..]
426                .iter()
427                .take_while(|(s, _)| s == short_bytes)
428                .map(to_key_entry_pair)
429                .filter(|(k, _)| prefix.matches(k));
430            collect(range, entry_mapper)
431        } else {
432            // Otherwise, use prefix.matches() to deal with odd byte. Since the prefix is
433            // covered by short key width, we're sure that the matching prefixes are sorted.
434            let pos = self.index.partition_point(|(s, _)| &s[..] < min_bytes);
435            let range = self.index[pos..]
436                .iter()
437                .map(to_key_entry_pair)
438                .take_while(|(k, _)| prefix.matches(k));
439            collect(range, entry_mapper)
440        }
441    }
442
443    /// Looks up unambiguous key with the given prefix.
444    pub fn resolve_prefix_to_key<S>(&self, source: S, prefix: &HexPrefix) -> PrefixResolution<K>
445    where
446        S: IdIndexSource<P>,
447        S::Entry: IdIndexSourceEntry<K>,
448    {
449        self.resolve_prefix_with(source, prefix, |_| ())
450            .map(|(key, ())| key)
451    }
452
453    /// Looks up entry for the key. Returns accessor to neighbors.
454    pub fn lookup_exact<'i, 'q, S>(
455        &'i self,
456        source: S,
457        key: &'q K,
458    ) -> Option<IdIndexLookup<'i, 'q, K, P, S, N>>
459    where
460        S: IdIndexSource<P>,
461        S::Entry: IdIndexSourceEntry<K>,
462    {
463        let lookup = self.lookup_some(source, key);
464        lookup.has_key().then_some(lookup)
465    }
466
467    fn lookup_some<'i, 'q, S>(&'i self, source: S, key: &'q K) -> IdIndexLookup<'i, 'q, K, P, S, N>
468    where
469        S: IdIndexSource<P>,
470    {
471        let short_key = unwrap_as_short_key(key.as_bytes());
472        let index = &self.index;
473        let pos = index.partition_point(|(s, _)| s < short_key);
474        IdIndexLookup {
475            index,
476            source,
477            key,
478            pos,
479        }
480    }
481
482    /// This function returns the shortest length of a prefix of `key` that
483    /// disambiguates it from every other key in the index.
484    ///
485    /// The length to be returned is a number of hexadecimal digits.
486    ///
487    /// This has some properties that we do not currently make much use of:
488    ///
489    /// - The algorithm works even if `key` itself is not in the index.
490    ///
491    /// - In the special case when there are keys in the trie for which our
492    ///   `key` is an exact prefix, returns `key.len() + 1`. Conceptually, in
493    ///   order to disambiguate, you need every letter of the key *and* the
494    ///   additional fact that it's the entire key). This case is extremely
495    ///   unlikely for hashes with 12+ hexadecimal characters.
496    pub fn shortest_unique_prefix_len<S>(&self, source: S, key: &K) -> usize
497    where
498        S: IdIndexSource<P>,
499        S::Entry: IdIndexSourceEntry<K>,
500    {
501        self.lookup_some(source, key).shortest_unique_prefix_len()
502    }
503}
504
505#[derive(Clone, Copy, Debug)]
506pub struct IdIndexLookup<'i, 'q, K, P, S, const N: usize> {
507    index: &'i Vec<([u8; N], P)>,
508    source: S,
509    key: &'q K,
510    pos: usize, // may be index.len()
511}
512
513impl<K, P, S, const N: usize> IdIndexLookup<'_, '_, K, P, S, N>
514where
515    K: ObjectId + Eq,
516    S: IdIndexSource<P>,
517    S::Entry: IdIndexSourceEntry<K>,
518{
519    fn has_key(&self) -> bool {
520        let short_key = unwrap_as_short_key(self.key.as_bytes());
521        self.index[self.pos..]
522            .iter()
523            .take_while(|(s, _)| s == short_key)
524            .any(|(_, p)| self.source.entry_at(p).to_key() == *self.key)
525    }
526
527    pub fn shortest_unique_prefix_len(&self) -> usize {
528        // Since entries having the same short key aren't sorted by the full-length key,
529        // we need to scan all entries in the current chunk, plus left/right neighbors.
530        // Typically, current.len() is 1.
531        let short_key = unwrap_as_short_key(self.key.as_bytes());
532        let left = self.pos.checked_sub(1).map(|p| &self.index[p]);
533        let (current, right) = {
534            let range = &self.index[self.pos..];
535            let count = range.iter().take_while(|(s, _)| s == short_key).count();
536            (&range[..count], range.get(count))
537        };
538
539        // Left/right neighbors should have unique short keys. For the current chunk,
540        // we need to look up full-length keys.
541        let unique_len = |a: &[u8], b: &[u8]| hex_util::common_hex_len(a, b) + 1;
542        let neighbor_lens = left
543            .iter()
544            .chain(&right)
545            .map(|(s, _)| unique_len(s, short_key));
546        let current_lens = current
547            .iter()
548            .map(|(_, p)| self.source.entry_at(p).to_key())
549            .filter(|key| key != self.key)
550            .map(|key| unique_len(key.as_bytes(), self.key.as_bytes()));
551        // Even if the key is the only one in the index, we require at least one digit.
552        neighbor_lens.chain(current_lens).max().unwrap_or(1)
553    }
554}
555
556fn unwrap_as_short_key<const N: usize>(key_bytes: &[u8]) -> &[u8; N] {
557    let short_slice = key_bytes.get(..N).expect("key too short");
558    short_slice.try_into().unwrap()
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564
565    #[derive(Clone, Copy, Eq, PartialEq)]
566    struct Position(usize);
567
568    impl<'a, V> IdIndexSource<Position> for &'a [(ChangeId, V)] {
569        type Entry = &'a (ChangeId, V);
570
571        fn entry_at(&self, pointer: &Position) -> Self::Entry {
572            &self[pointer.0]
573        }
574    }
575
576    impl<V> IdIndexSourceEntry<ChangeId> for &'_ (ChangeId, V) {
577        fn to_key(&self) -> ChangeId {
578            let (change_id, _) = self;
579            change_id.clone()
580        }
581    }
582
583    fn build_id_index<V, const N: usize>(
584        entries: &[(ChangeId, V)],
585    ) -> IdIndex<ChangeId, Position, N> {
586        let mut builder = IdIndex::with_capacity(entries.len());
587        for (i, (k, _)) in entries.iter().enumerate() {
588            builder.insert(k, Position(i));
589        }
590        builder.build()
591    }
592
593    #[test]
594    fn test_id_index_resolve_prefix() {
595        let source = vec![
596            (ChangeId::from_hex("0000"), 0),
597            (ChangeId::from_hex("0099"), 1),
598            (ChangeId::from_hex("0099"), 2),
599            (ChangeId::from_hex("0aaa"), 3),
600            (ChangeId::from_hex("0aab"), 4),
601        ];
602
603        // short_key.len() == full_key.len()
604        let id_index = build_id_index::<_, 2>(&source);
605        let resolve_prefix = |prefix: &HexPrefix| {
606            let resolution: PrefixResolution<(_, Vec<_>)> =
607                id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
608            resolution.map(|(key, mut values)| {
609                values.sort_unstable(); // order of values might not be preserved by IdIndex
610                (key, values)
611            })
612        };
613        assert_eq!(
614            resolve_prefix(&HexPrefix::try_from_hex("0").unwrap()),
615            PrefixResolution::AmbiguousMatch,
616        );
617        assert_eq!(
618            resolve_prefix(&HexPrefix::try_from_hex("00").unwrap()),
619            PrefixResolution::AmbiguousMatch,
620        );
621        assert_eq!(
622            resolve_prefix(&HexPrefix::try_from_hex("000").unwrap()),
623            PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
624        );
625        assert_eq!(
626            resolve_prefix(&HexPrefix::try_from_hex("0001").unwrap()),
627            PrefixResolution::NoMatch,
628        );
629        assert_eq!(
630            resolve_prefix(&HexPrefix::try_from_hex("009").unwrap()),
631            PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
632        );
633        assert_eq!(
634            resolve_prefix(&HexPrefix::try_from_hex("0aa").unwrap()),
635            PrefixResolution::AmbiguousMatch,
636        );
637        assert_eq!(
638            resolve_prefix(&HexPrefix::try_from_hex("0aab").unwrap()),
639            PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
640        );
641        assert_eq!(
642            resolve_prefix(&HexPrefix::try_from_hex("f").unwrap()),
643            PrefixResolution::NoMatch,
644        );
645
646        // short_key.len() < full_key.len()
647        let id_index = build_id_index::<_, 1>(&source);
648        let resolve_prefix = |prefix: &HexPrefix| {
649            let resolution: PrefixResolution<(_, Vec<_>)> =
650                id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
651            resolution.map(|(key, mut values)| {
652                values.sort_unstable(); // order of values might not be preserved by IdIndex
653                (key, values)
654            })
655        };
656        assert_eq!(
657            resolve_prefix(&HexPrefix::try_from_hex("00").unwrap()),
658            PrefixResolution::AmbiguousMatch,
659        );
660        assert_eq!(
661            resolve_prefix(&HexPrefix::try_from_hex("000").unwrap()),
662            PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
663        );
664        assert_eq!(
665            resolve_prefix(&HexPrefix::try_from_hex("0001").unwrap()),
666            PrefixResolution::NoMatch,
667        );
668        // For short key "00", ["0000", "0099", "0099"] would match. We shouldn't
669        // break at "009".matches("0000").
670        assert_eq!(
671            resolve_prefix(&HexPrefix::try_from_hex("009").unwrap()),
672            PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
673        );
674        assert_eq!(
675            resolve_prefix(&HexPrefix::try_from_hex("0a").unwrap()),
676            PrefixResolution::AmbiguousMatch,
677        );
678        assert_eq!(
679            resolve_prefix(&HexPrefix::try_from_hex("0aa").unwrap()),
680            PrefixResolution::AmbiguousMatch,
681        );
682        assert_eq!(
683            resolve_prefix(&HexPrefix::try_from_hex("0aab").unwrap()),
684            PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
685        );
686    }
687
688    #[test]
689    fn test_lookup_exact() {
690        // No crash if empty
691        let source: Vec<(ChangeId, ())> = vec![];
692        let id_index = build_id_index::<_, 1>(&source);
693        assert!(
694            id_index
695                .lookup_exact(&*source, &ChangeId::from_hex("00"))
696                .is_none()
697        );
698
699        let source = vec![
700            (ChangeId::from_hex("ab00"), ()),
701            (ChangeId::from_hex("ab01"), ()),
702        ];
703        let id_index = build_id_index::<_, 1>(&source);
704        assert!(
705            id_index
706                .lookup_exact(&*source, &ChangeId::from_hex("aa00"))
707                .is_none()
708        );
709        assert!(
710            id_index
711                .lookup_exact(&*source, &ChangeId::from_hex("ab00"))
712                .is_some()
713        );
714        assert!(
715            id_index
716                .lookup_exact(&*source, &ChangeId::from_hex("ab01"))
717                .is_some()
718        );
719        assert!(
720            id_index
721                .lookup_exact(&*source, &ChangeId::from_hex("ab02"))
722                .is_none()
723        );
724        assert!(
725            id_index
726                .lookup_exact(&*source, &ChangeId::from_hex("ac00"))
727                .is_none()
728        );
729    }
730
731    #[test]
732    fn test_id_index_shortest_unique_prefix_len() {
733        // No crash if empty
734        let source: Vec<(ChangeId, ())> = vec![];
735        let id_index = build_id_index::<_, 1>(&source);
736        assert_eq!(
737            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("00")),
738            1
739        );
740
741        let source = vec![
742            (ChangeId::from_hex("ab"), ()),
743            (ChangeId::from_hex("acd0"), ()),
744            (ChangeId::from_hex("acd0"), ()), // duplicated key is allowed
745        ];
746        let id_index = build_id_index::<_, 1>(&source);
747        assert_eq!(
748            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
749            2
750        );
751        assert_eq!(
752            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ac")),
753            3
754        );
755
756        let source = vec![
757            (ChangeId::from_hex("ab"), ()),
758            (ChangeId::from_hex("acd0"), ()),
759            (ChangeId::from_hex("acf0"), ()),
760            (ChangeId::from_hex("a0"), ()),
761            (ChangeId::from_hex("ba"), ()),
762        ];
763        let id_index = build_id_index::<_, 1>(&source);
764
765        assert_eq!(
766            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("a0")),
767            2
768        );
769        assert_eq!(
770            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ba")),
771            1
772        );
773        assert_eq!(
774            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ab")),
775            2
776        );
777        assert_eq!(
778            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
779            3
780        );
781        // If it were there, the length would be 1.
782        assert_eq!(
783            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("c0")),
784            1
785        );
786
787        let source = vec![
788            (ChangeId::from_hex("000000"), ()),
789            (ChangeId::from_hex("01ffff"), ()),
790            (ChangeId::from_hex("010000"), ()),
791            (ChangeId::from_hex("01fffe"), ()),
792            (ChangeId::from_hex("ffffff"), ()),
793        ];
794        let id_index = build_id_index::<_, 1>(&source);
795        // Multiple candidates in the current chunk "01"
796        assert_eq!(
797            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01ffff")),
798            6
799        );
800        assert_eq!(
801            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("010000")),
802            3
803        );
804        assert_eq!(
805            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01fffe")),
806            6
807        );
808        // Only right neighbor
809        assert_eq!(
810            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("000000")),
811            2
812        );
813        // Only left neighbor
814        assert_eq!(
815            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ffffff")),
816            1
817        );
818    }
819}