1#![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#[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 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
149pub struct IdPrefixIndex<'a> {
151 indexes: Option<&'a Indexes>,
152}
153
154impl IdPrefixIndex<'_> {
155 pub const fn empty() -> IdPrefixIndex<'static> {
157 IdPrefixIndex { indexes: None }
158 }
159
160 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 }
174 PrefixResolution::SingleMatch(id) => {
175 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 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 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 }
237 PrefixResolution::SingleMatch(change_id) => {
238 return match repo.resolve_change_id(&change_id).await? {
239 Some(commit_ids) => Ok(PrefixResolution::SingleMatch(commit_ids)),
241 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 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 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 .unwrap_or(id_sym.len())
299}
300
301#[derive(Clone, Debug)]
314pub struct IdIndex<K, P, const N: usize> {
315 index: Vec<([u8; N], P)>,
318 phantom_key: PhantomData<K>,
321}
322
323pub trait IdIndexSource<P> {
325 type Entry;
326
327 fn entry_at(&self, pointer: &P) -> Self::Entry;
328}
329
330pub 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 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 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 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 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 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 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 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 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, }
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 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 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 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 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(); (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 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(); (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 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 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 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"), ()), ];
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 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 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 assert_eq!(
814 id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("000000")),
815 2
816 );
817 assert_eq!(
819 id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ffffff")),
820 1
821 );
822 }
823}