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).block_on()? {
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).block_on()
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()
219            .shortest_unique_commit_id_prefix_len(commit_id)
220            .block_on()
221    }
222
223    /// Resolve an unambiguous change ID prefix to the commit IDs in the revset.
224    pub async fn resolve_change_prefix(
225        &self,
226        repo: &dyn Repo,
227        prefix: &HexPrefix,
228    ) -> IndexResult<PrefixResolution<ResolvedChangeTargets>> {
229        if let Some(indexes) = self.indexes {
230            let resolution = indexes
231                .change_index
232                .resolve_prefix_to_key(&*indexes.commit_change_ids, prefix);
233            match resolution {
234                PrefixResolution::NoMatch => {
235                    // Fall back to resolving in entire repo
236                }
237                PrefixResolution::SingleMatch(change_id) => {
238                    return match repo.resolve_change_id(&change_id).await? {
239                        // There may be more commits with this change id outside the narrower sets.
240                        Some(commit_ids) => Ok(PrefixResolution::SingleMatch(commit_ids)),
241                        // The disambiguation set may contain hidden commits.
242                        None => Ok(PrefixResolution::NoMatch),
243                    };
244                }
245                PrefixResolution::AmbiguousMatch => {
246                    return Ok(PrefixResolution::AmbiguousMatch);
247                }
248            }
249        }
250        repo.resolve_change_id_prefix(prefix).await
251    }
252
253    /// Returns the shortest length of a prefix of `change_id` that can still be
254    /// resolved by `resolve_change_prefix()` and [`SymbolResolver`].
255    pub async fn shortest_change_prefix_len(
256        &self,
257        repo: &dyn Repo,
258        change_id: &ChangeId,
259    ) -> IndexResult<usize> {
260        let len = self
261            .shortest_change_prefix_len_exact(repo, change_id)
262            .await?;
263        Ok(disambiguate_prefix_with_refs(
264            repo.view(),
265            &change_id.to_string(),
266            len,
267        ))
268    }
269
270    async fn shortest_change_prefix_len_exact(
271        &self,
272        repo: &dyn Repo,
273        change_id: &ChangeId,
274    ) -> IndexResult<usize> {
275        if let Some(indexes) = self.indexes
276            && let Some(lookup) = indexes
277                .change_index
278                .lookup_exact(&*indexes.commit_change_ids, change_id)
279        {
280            return Ok(lookup.shortest_unique_prefix_len());
281        }
282        repo.shortest_unique_change_id_prefix_len(change_id).await
283    }
284}
285
286fn disambiguate_prefix_with_refs(view: &View, id_sym: &str, min_len: usize) -> usize {
287    debug_assert!(id_sym.is_ascii());
288    (min_len..id_sym.len())
289        .find(|&n| {
290            // Tags and bookmarks have higher priority. Extension symbols have
291            // lower priority.
292            let prefix = &id_sym[..n];
293            view.get_local_tag(prefix.as_ref()).is_absent()
294                && view.get_local_bookmark(prefix.as_ref()).is_absent()
295        })
296        // No need to test conflicts with the full ID. We have to return some
297        // valid length anyway.
298        .unwrap_or(id_sym.len())
299}
300
301/// In-memory immutable index to do prefix lookup of key `K` through `P`.
302///
303/// In a nutshell, this is a mapping of `K` -> `P` -> `S::Entry` where `S:
304/// IdIndexSource<P>`. The source table `S` isn't owned by this index.
305///
306/// This index stores first `N` bytes of each key `K` associated with the
307/// pointer `P`. `K` may be a heap-allocated object. `P` is supposed to be
308/// a cheap value type like `u32` or `usize`. As the index entry of type
309/// `([u8; N], P)` is small and has no indirect reference, constructing
310/// the index should be faster than sorting the source `(K, _)` pairs.
311///
312/// A key `K` must be at least `N` bytes long.
313#[derive(Clone, Debug)]
314pub struct IdIndex<K, P, const N: usize> {
315    // Maybe better to build separate (keys, values) vectors, but there's no std function
316    // to co-sort them.
317    index: Vec<([u8; N], P)>,
318    // Let's pretend [u8; N] above were of type K. It helps type inference, and ensures that
319    // IdIndexSource has the same key type.
320    phantom_key: PhantomData<K>,
321}
322
323/// Source table for `IdIndex` to map pointer of type `P` to entry.
324pub trait IdIndexSource<P> {
325    type Entry;
326
327    fn entry_at(&self, pointer: &P) -> Self::Entry;
328}
329
330/// Source table entry of `IdIndex`, which is conceptually a `(key, value)`
331/// pair.
332pub trait IdIndexSourceEntry<K> {
333    fn to_key(&self) -> K;
334}
335
336#[derive(Clone, Debug)]
337pub struct IdIndexBuilder<K, P, const N: usize> {
338    unsorted_index: Vec<([u8; N], P)>,
339    phantom_key: PhantomData<K>,
340}
341
342impl<K, P, const N: usize> IdIndexBuilder<K, P, N>
343where
344    K: ObjectId + Ord,
345{
346    /// Inserts new entry. Multiple values can be associated with a single key.
347    pub fn insert(&mut self, key: &K, pointer: P) {
348        let short_key = unwrap_as_short_key(key.as_bytes());
349        self.unsorted_index.push((*short_key, pointer));
350    }
351
352    pub fn build(self) -> IdIndex<K, P, N> {
353        let mut index = self.unsorted_index;
354        index.sort_unstable_by_key(|(s, _)| *s);
355        let phantom_key = self.phantom_key;
356        IdIndex { index, phantom_key }
357    }
358}
359
360impl<K, P, const N: usize> IdIndex<K, P, N>
361where
362    K: ObjectId + Ord,
363{
364    pub fn builder() -> IdIndexBuilder<K, P, N> {
365        IdIndexBuilder {
366            unsorted_index: Vec::new(),
367            phantom_key: PhantomData,
368        }
369    }
370
371    pub fn with_capacity(capacity: usize) -> IdIndexBuilder<K, P, N> {
372        IdIndexBuilder {
373            unsorted_index: Vec::with_capacity(capacity),
374            phantom_key: PhantomData,
375        }
376    }
377
378    /// Looks up entries with the given prefix, and collects values if matched
379    /// entries have unambiguous keys.
380    pub fn resolve_prefix_with<B, S, U>(
381        &self,
382        source: S,
383        prefix: &HexPrefix,
384        entry_mapper: impl FnMut(S::Entry) -> U,
385    ) -> PrefixResolution<(K, B)>
386    where
387        B: FromIterator<U>,
388        S: IdIndexSource<P>,
389        S::Entry: IdIndexSourceEntry<K>,
390    {
391        fn collect<B, K, E, U>(
392            mut range: impl Iterator<Item = (K, E)>,
393            mut entry_mapper: impl FnMut(E) -> U,
394        ) -> PrefixResolution<(K, B)>
395        where
396            B: FromIterator<U>,
397            K: Eq,
398        {
399            if let Some((first_key, first_entry)) = range.next() {
400                let maybe_values: Option<B> = iter::once(Some(entry_mapper(first_entry)))
401                    .chain(range.map(|(k, e)| (k == first_key).then(|| entry_mapper(e))))
402                    .collect();
403                if let Some(values) = maybe_values {
404                    PrefixResolution::SingleMatch((first_key, values))
405                } else {
406                    PrefixResolution::AmbiguousMatch
407                }
408            } else {
409                PrefixResolution::NoMatch
410            }
411        }
412
413        let min_bytes = prefix.min_prefix_bytes();
414        if min_bytes.is_empty() {
415            // We consider an empty prefix ambiguous even if the index has a single entry.
416            return PrefixResolution::AmbiguousMatch;
417        }
418
419        let to_key_entry_pair = |(_, pointer): &(_, P)| -> (K, S::Entry) {
420            let entry = source.entry_at(pointer);
421            (entry.to_key(), entry)
422        };
423        if min_bytes.len() > N {
424            // If the min prefix (including odd byte) is longer than the stored short keys,
425            // we are sure that min_bytes[..N] does not include the odd byte. Use it to
426            // take contiguous range, then filter by (longer) prefix.matches().
427            let short_bytes = unwrap_as_short_key(min_bytes);
428            let pos = self.index.partition_point(|(s, _)| s < short_bytes);
429            let range = self.index[pos..]
430                .iter()
431                .take_while(|(s, _)| s == short_bytes)
432                .map(to_key_entry_pair)
433                .filter(|(k, _)| prefix.matches(k));
434            collect(range, entry_mapper)
435        } else {
436            // Otherwise, use prefix.matches() to deal with odd byte. Since the prefix is
437            // covered by short key width, we're sure that the matching prefixes are sorted.
438            let pos = self.index.partition_point(|(s, _)| &s[..] < min_bytes);
439            let range = self.index[pos..]
440                .iter()
441                .map(to_key_entry_pair)
442                .take_while(|(k, _)| prefix.matches(k));
443            collect(range, entry_mapper)
444        }
445    }
446
447    /// Looks up unambiguous key with the given prefix.
448    pub fn resolve_prefix_to_key<S>(&self, source: S, prefix: &HexPrefix) -> PrefixResolution<K>
449    where
450        S: IdIndexSource<P>,
451        S::Entry: IdIndexSourceEntry<K>,
452    {
453        self.resolve_prefix_with(source, prefix, |_| ())
454            .map(|(key, ())| key)
455    }
456
457    /// Looks up entry for the key. Returns accessor to neighbors.
458    pub fn lookup_exact<'i, 'q, S>(
459        &'i self,
460        source: S,
461        key: &'q K,
462    ) -> Option<IdIndexLookup<'i, 'q, K, P, S, N>>
463    where
464        S: IdIndexSource<P>,
465        S::Entry: IdIndexSourceEntry<K>,
466    {
467        let lookup = self.lookup_some(source, key);
468        lookup.has_key().then_some(lookup)
469    }
470
471    fn lookup_some<'i, 'q, S>(&'i self, source: S, key: &'q K) -> IdIndexLookup<'i, 'q, K, P, S, N>
472    where
473        S: IdIndexSource<P>,
474    {
475        let short_key = unwrap_as_short_key(key.as_bytes());
476        let index = &self.index;
477        let pos = index.partition_point(|(s, _)| s < short_key);
478        IdIndexLookup {
479            index,
480            source,
481            key,
482            pos,
483        }
484    }
485
486    /// This function returns the shortest length of a prefix of `key` that
487    /// disambiguates it from every other key in the index.
488    ///
489    /// The length to be returned is a number of hexadecimal digits.
490    ///
491    /// This has some properties that we do not currently make much use of:
492    ///
493    /// - The algorithm works even if `key` itself is not in the index.
494    ///
495    /// - In the special case when there are keys in the trie for which our
496    ///   `key` is an exact prefix, returns `key.len() + 1`. Conceptually, in
497    ///   order to disambiguate, you need every letter of the key *and* the
498    ///   additional fact that it's the entire key). This case is extremely
499    ///   unlikely for hashes with 12+ hexadecimal characters.
500    pub fn shortest_unique_prefix_len<S>(&self, source: S, key: &K) -> usize
501    where
502        S: IdIndexSource<P>,
503        S::Entry: IdIndexSourceEntry<K>,
504    {
505        self.lookup_some(source, key).shortest_unique_prefix_len()
506    }
507}
508
509#[derive(Clone, Copy, Debug)]
510pub struct IdIndexLookup<'i, 'q, K, P, S, const N: usize> {
511    index: &'i Vec<([u8; N], P)>,
512    source: S,
513    key: &'q K,
514    pos: usize, // may be index.len()
515}
516
517impl<K, P, S, const N: usize> IdIndexLookup<'_, '_, K, P, S, N>
518where
519    K: ObjectId + Eq,
520    S: IdIndexSource<P>,
521    S::Entry: IdIndexSourceEntry<K>,
522{
523    fn has_key(&self) -> bool {
524        let short_key = unwrap_as_short_key(self.key.as_bytes());
525        self.index[self.pos..]
526            .iter()
527            .take_while(|(s, _)| s == short_key)
528            .any(|(_, p)| self.source.entry_at(p).to_key() == *self.key)
529    }
530
531    pub fn shortest_unique_prefix_len(&self) -> usize {
532        // Since entries having the same short key aren't sorted by the full-length key,
533        // we need to scan all entries in the current chunk, plus left/right neighbors.
534        // Typically, current.len() is 1.
535        let short_key = unwrap_as_short_key(self.key.as_bytes());
536        let left = self.pos.checked_sub(1).map(|p| &self.index[p]);
537        let (current, right) = {
538            let range = &self.index[self.pos..];
539            let count = range.iter().take_while(|(s, _)| s == short_key).count();
540            (&range[..count], range.get(count))
541        };
542
543        // Left/right neighbors should have unique short keys. For the current chunk,
544        // we need to look up full-length keys.
545        let unique_len = |a: &[u8], b: &[u8]| hex_util::common_hex_len(a, b) + 1;
546        let neighbor_lens = left
547            .iter()
548            .chain(&right)
549            .map(|(s, _)| unique_len(s, short_key));
550        let current_lens = current
551            .iter()
552            .map(|(_, p)| self.source.entry_at(p).to_key())
553            .filter(|key| key != self.key)
554            .map(|key| unique_len(key.as_bytes(), self.key.as_bytes()));
555        // Even if the key is the only one in the index, we require at least one digit.
556        neighbor_lens.chain(current_lens).max().unwrap_or(1)
557    }
558}
559
560fn unwrap_as_short_key<const N: usize>(key_bytes: &[u8]) -> &[u8; N] {
561    let short_slice = key_bytes.get(..N).expect("key too short");
562    short_slice.try_into().unwrap()
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568
569    #[derive(Clone, Copy, Eq, PartialEq)]
570    struct Position(usize);
571
572    impl<'a, V> IdIndexSource<Position> for &'a [(ChangeId, V)] {
573        type Entry = &'a (ChangeId, V);
574
575        fn entry_at(&self, pointer: &Position) -> Self::Entry {
576            &self[pointer.0]
577        }
578    }
579
580    impl<V> IdIndexSourceEntry<ChangeId> for &'_ (ChangeId, V) {
581        fn to_key(&self) -> ChangeId {
582            let (change_id, _) = self;
583            change_id.clone()
584        }
585    }
586
587    fn build_id_index<V, const N: usize>(
588        entries: &[(ChangeId, V)],
589    ) -> IdIndex<ChangeId, Position, N> {
590        let mut builder = IdIndex::with_capacity(entries.len());
591        for (i, (k, _)) in entries.iter().enumerate() {
592            builder.insert(k, Position(i));
593        }
594        builder.build()
595    }
596
597    #[test]
598    fn test_id_index_resolve_prefix() {
599        let source = vec![
600            (ChangeId::from_hex("0000"), 0),
601            (ChangeId::from_hex("0099"), 1),
602            (ChangeId::from_hex("0099"), 2),
603            (ChangeId::from_hex("0aaa"), 3),
604            (ChangeId::from_hex("0aab"), 4),
605        ];
606
607        // short_key.len() == full_key.len()
608        let id_index = build_id_index::<_, 2>(&source);
609        let resolve_prefix = |prefix: &HexPrefix| {
610            let resolution: PrefixResolution<(_, Vec<_>)> =
611                id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
612            resolution.map(|(key, mut values)| {
613                values.sort_unstable(); // order of values might not be preserved by IdIndex
614                (key, values)
615            })
616        };
617        assert_eq!(
618            resolve_prefix(&HexPrefix::try_from_hex("0").unwrap()),
619            PrefixResolution::AmbiguousMatch,
620        );
621        assert_eq!(
622            resolve_prefix(&HexPrefix::try_from_hex("00").unwrap()),
623            PrefixResolution::AmbiguousMatch,
624        );
625        assert_eq!(
626            resolve_prefix(&HexPrefix::try_from_hex("000").unwrap()),
627            PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
628        );
629        assert_eq!(
630            resolve_prefix(&HexPrefix::try_from_hex("0001").unwrap()),
631            PrefixResolution::NoMatch,
632        );
633        assert_eq!(
634            resolve_prefix(&HexPrefix::try_from_hex("009").unwrap()),
635            PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
636        );
637        assert_eq!(
638            resolve_prefix(&HexPrefix::try_from_hex("0aa").unwrap()),
639            PrefixResolution::AmbiguousMatch,
640        );
641        assert_eq!(
642            resolve_prefix(&HexPrefix::try_from_hex("0aab").unwrap()),
643            PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
644        );
645        assert_eq!(
646            resolve_prefix(&HexPrefix::try_from_hex("f").unwrap()),
647            PrefixResolution::NoMatch,
648        );
649
650        // short_key.len() < full_key.len()
651        let id_index = build_id_index::<_, 1>(&source);
652        let resolve_prefix = |prefix: &HexPrefix| {
653            let resolution: PrefixResolution<(_, Vec<_>)> =
654                id_index.resolve_prefix_with(&*source, prefix, |(_, v)| *v);
655            resolution.map(|(key, mut values)| {
656                values.sort_unstable(); // order of values might not be preserved by IdIndex
657                (key, values)
658            })
659        };
660        assert_eq!(
661            resolve_prefix(&HexPrefix::try_from_hex("00").unwrap()),
662            PrefixResolution::AmbiguousMatch,
663        );
664        assert_eq!(
665            resolve_prefix(&HexPrefix::try_from_hex("000").unwrap()),
666            PrefixResolution::SingleMatch((ChangeId::from_hex("0000"), vec![0])),
667        );
668        assert_eq!(
669            resolve_prefix(&HexPrefix::try_from_hex("0001").unwrap()),
670            PrefixResolution::NoMatch,
671        );
672        // For short key "00", ["0000", "0099", "0099"] would match. We shouldn't
673        // break at "009".matches("0000").
674        assert_eq!(
675            resolve_prefix(&HexPrefix::try_from_hex("009").unwrap()),
676            PrefixResolution::SingleMatch((ChangeId::from_hex("0099"), vec![1, 2])),
677        );
678        assert_eq!(
679            resolve_prefix(&HexPrefix::try_from_hex("0a").unwrap()),
680            PrefixResolution::AmbiguousMatch,
681        );
682        assert_eq!(
683            resolve_prefix(&HexPrefix::try_from_hex("0aa").unwrap()),
684            PrefixResolution::AmbiguousMatch,
685        );
686        assert_eq!(
687            resolve_prefix(&HexPrefix::try_from_hex("0aab").unwrap()),
688            PrefixResolution::SingleMatch((ChangeId::from_hex("0aab"), vec![4])),
689        );
690    }
691
692    #[test]
693    fn test_lookup_exact() {
694        // No crash if empty
695        let source: Vec<(ChangeId, ())> = vec![];
696        let id_index = build_id_index::<_, 1>(&source);
697        assert!(
698            id_index
699                .lookup_exact(&*source, &ChangeId::from_hex("00"))
700                .is_none()
701        );
702
703        let source = vec![
704            (ChangeId::from_hex("ab00"), ()),
705            (ChangeId::from_hex("ab01"), ()),
706        ];
707        let id_index = build_id_index::<_, 1>(&source);
708        assert!(
709            id_index
710                .lookup_exact(&*source, &ChangeId::from_hex("aa00"))
711                .is_none()
712        );
713        assert!(
714            id_index
715                .lookup_exact(&*source, &ChangeId::from_hex("ab00"))
716                .is_some()
717        );
718        assert!(
719            id_index
720                .lookup_exact(&*source, &ChangeId::from_hex("ab01"))
721                .is_some()
722        );
723        assert!(
724            id_index
725                .lookup_exact(&*source, &ChangeId::from_hex("ab02"))
726                .is_none()
727        );
728        assert!(
729            id_index
730                .lookup_exact(&*source, &ChangeId::from_hex("ac00"))
731                .is_none()
732        );
733    }
734
735    #[test]
736    fn test_id_index_shortest_unique_prefix_len() {
737        // No crash if empty
738        let source: Vec<(ChangeId, ())> = vec![];
739        let id_index = build_id_index::<_, 1>(&source);
740        assert_eq!(
741            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("00")),
742            1
743        );
744
745        let source = vec![
746            (ChangeId::from_hex("ab"), ()),
747            (ChangeId::from_hex("acd0"), ()),
748            (ChangeId::from_hex("acd0"), ()), // duplicated key is allowed
749        ];
750        let id_index = build_id_index::<_, 1>(&source);
751        assert_eq!(
752            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
753            2
754        );
755        assert_eq!(
756            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ac")),
757            3
758        );
759
760        let source = vec![
761            (ChangeId::from_hex("ab"), ()),
762            (ChangeId::from_hex("acd0"), ()),
763            (ChangeId::from_hex("acf0"), ()),
764            (ChangeId::from_hex("a0"), ()),
765            (ChangeId::from_hex("ba"), ()),
766        ];
767        let id_index = build_id_index::<_, 1>(&source);
768
769        assert_eq!(
770            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("a0")),
771            2
772        );
773        assert_eq!(
774            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ba")),
775            1
776        );
777        assert_eq!(
778            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ab")),
779            2
780        );
781        assert_eq!(
782            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("acd0")),
783            3
784        );
785        // If it were there, the length would be 1.
786        assert_eq!(
787            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("c0")),
788            1
789        );
790
791        let source = vec![
792            (ChangeId::from_hex("000000"), ()),
793            (ChangeId::from_hex("01ffff"), ()),
794            (ChangeId::from_hex("010000"), ()),
795            (ChangeId::from_hex("01fffe"), ()),
796            (ChangeId::from_hex("ffffff"), ()),
797        ];
798        let id_index = build_id_index::<_, 1>(&source);
799        // Multiple candidates in the current chunk "01"
800        assert_eq!(
801            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01ffff")),
802            6
803        );
804        assert_eq!(
805            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("010000")),
806            3
807        );
808        assert_eq!(
809            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("01fffe")),
810            6
811        );
812        // Only right neighbor
813        assert_eq!(
814            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("000000")),
815            2
816        );
817        // Only left neighbor
818        assert_eq!(
819            id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ffffff")),
820            1
821        );
822    }
823}