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)? {
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 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 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 }
235 PrefixResolution::SingleMatch(change_id) => {
236 return match repo.resolve_change_id(&change_id)? {
237 Some(commit_ids) => Ok(PrefixResolution::SingleMatch(commit_ids)),
239 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 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 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 .unwrap_or(id_sym.len())
295}
296
297#[derive(Clone, Debug)]
310pub struct IdIndex<K, P, const N: usize> {
311 index: Vec<([u8; N], P)>,
314 phantom_key: PhantomData<K>,
317}
318
319pub trait IdIndexSource<P> {
321 type Entry;
322
323 fn entry_at(&self, pointer: &P) -> Self::Entry;
324}
325
326pub 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 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 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 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 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 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 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 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 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, }
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 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 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 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 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(); (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 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(); (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 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 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 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"), ()), ];
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 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 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 assert_eq!(
810 id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("000000")),
811 2
812 );
813 assert_eq!(
815 id_index.shortest_unique_prefix_len(&*source, &ChangeId::from_hex("ffffff")),
816 1
817 );
818 }
819}