jj_lib/default_index/
readonly.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#![allow(missing_docs)]
16
17use std::any::Any;
18use std::cmp::Ordering;
19use std::fmt::Debug;
20use std::fmt::Formatter;
21use std::fs::File;
22use std::io;
23use std::io::Read;
24use std::path::Path;
25use std::sync::Arc;
26
27use smallvec::smallvec;
28use thiserror::Error;
29
30use super::composite::AsCompositeIndex;
31use super::composite::ChangeIdIndexImpl;
32use super::composite::CompositeIndex;
33use super::composite::IndexSegment;
34use super::entry::IndexPosition;
35use super::entry::LocalPosition;
36use super::entry::SmallIndexPositionsVec;
37use super::entry::SmallLocalPositionsVec;
38use super::mutable::DefaultMutableIndex;
39use crate::backend::ChangeId;
40use crate::backend::CommitId;
41use crate::index::AllHeadsForGcUnsupported;
42use crate::index::ChangeIdIndex;
43use crate::index::Index;
44use crate::index::IndexError;
45use crate::index::MutableIndex;
46use crate::index::ReadonlyIndex;
47use crate::object_id::HexPrefix;
48use crate::object_id::ObjectId;
49use crate::object_id::PrefixResolution;
50use crate::revset::ResolvedExpression;
51use crate::revset::Revset;
52use crate::revset::RevsetEvaluationError;
53use crate::store::Store;
54
55/// Error while loading index segment file.
56#[derive(Debug, Error)]
57pub enum ReadonlyIndexLoadError {
58    #[error("Unexpected index version")]
59    UnexpectedVersion {
60        found_version: u32,
61        expected_version: u32,
62    },
63    #[error("Failed to load commit index file '{name}'")]
64    Other {
65        /// Index file name.
66        name: String,
67        /// Underlying error.
68        #[source]
69        error: io::Error,
70    },
71}
72
73impl ReadonlyIndexLoadError {
74    fn invalid_data(
75        name: impl Into<String>,
76        error: impl Into<Box<dyn std::error::Error + Send + Sync>>,
77    ) -> Self {
78        Self::from_io_err(name, io::Error::new(io::ErrorKind::InvalidData, error))
79    }
80
81    fn from_io_err(name: impl Into<String>, error: io::Error) -> Self {
82        ReadonlyIndexLoadError::Other {
83            name: name.into(),
84            error,
85        }
86    }
87
88    /// Returns true if the underlying error suggests data corruption.
89    pub(super) fn is_corrupt_or_not_found(&self) -> bool {
90        match self {
91            ReadonlyIndexLoadError::UnexpectedVersion { .. } => true,
92            ReadonlyIndexLoadError::Other { name: _, error } => {
93                // If the parent file name field is corrupt, the file wouldn't be found.
94                // And there's no need to distinguish it from an empty file.
95                matches!(
96                    error.kind(),
97                    io::ErrorKind::NotFound
98                        | io::ErrorKind::InvalidData
99                        | io::ErrorKind::UnexpectedEof
100                )
101            }
102        }
103    }
104}
105
106/// Current format version of the index segment file.
107pub(crate) const INDEX_SEGMENT_FILE_FORMAT_VERSION: u32 = 6;
108
109/// If set, the value is stored in the overflow table.
110pub(crate) const OVERFLOW_FLAG: u32 = 0x8000_0000;
111
112/// Global index position of parent entry, or overflow pointer.
113#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
114struct ParentIndexPosition(u32);
115
116impl ParentIndexPosition {
117    fn as_inlined(self) -> Option<IndexPosition> {
118        (self.0 & OVERFLOW_FLAG == 0).then_some(IndexPosition(self.0))
119    }
120
121    fn as_overflow(self) -> Option<u32> {
122        (self.0 & OVERFLOW_FLAG != 0).then_some(!self.0)
123    }
124}
125
126/// Local position of entry pointed by change id, or overflow pointer.
127#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
128struct ChangeLocalPosition(u32);
129
130impl ChangeLocalPosition {
131    fn as_inlined(self) -> Option<LocalPosition> {
132        (self.0 & OVERFLOW_FLAG == 0).then_some(LocalPosition(self.0))
133    }
134
135    fn as_overflow(self) -> Option<u32> {
136        (self.0 & OVERFLOW_FLAG != 0).then_some(!self.0)
137    }
138}
139
140struct CommitGraphEntry<'a> {
141    data: &'a [u8],
142}
143
144// TODO: Add pointers to ancestors further back, like a skip list. Clear the
145// lowest set bit to determine which generation number the pointers point to.
146impl CommitGraphEntry<'_> {
147    fn size(commit_id_length: usize) -> usize {
148        16 + commit_id_length
149    }
150
151    fn generation_number(&self) -> u32 {
152        u32::from_le_bytes(self.data[0..4].try_into().unwrap())
153    }
154
155    fn parent1_pos_or_overflow_pos(&self) -> ParentIndexPosition {
156        ParentIndexPosition(u32::from_le_bytes(self.data[4..8].try_into().unwrap()))
157    }
158
159    fn parent2_pos_or_overflow_len(&self) -> ParentIndexPosition {
160        ParentIndexPosition(u32::from_le_bytes(self.data[8..12].try_into().unwrap()))
161    }
162
163    fn change_id_lookup_pos(&self) -> u32 {
164        u32::from_le_bytes(self.data[12..16].try_into().unwrap())
165    }
166
167    fn commit_id(&self) -> CommitId {
168        CommitId::from_bytes(self.commit_id_bytes())
169    }
170
171    // might be better to add borrowed version of CommitId
172    fn commit_id_bytes(&self) -> &[u8] {
173        &self.data[16..]
174    }
175}
176
177/// Commit index segment backed by immutable file.
178///
179/// File format:
180/// ```text
181/// u32: file format version
182/// u32: parent segment file name length (0 means root)
183/// <length number of bytes>: parent segment file name
184///
185/// u32: number of local commit entries
186/// u32: number of local change ids
187/// u32: number of overflow parent entries
188/// u32: number of overflow change id positions
189/// for each entry, in some topological order with parents first:
190///   u32: generation number
191///   if number of parents <= 2:
192///     u32: (< 0x8000_0000) global index position for parent 1
193///          (==0xffff_ffff) no parent 1
194///     u32: (< 0x8000_0000) global index position for parent 2
195///          (==0xffff_ffff) no parent 2
196///   else:
197///     u32: (>=0x8000_0000) position in the overflow table, bit-negated
198///     u32: (>=0x8000_0000) number of parents (in the overflow table), bit-negated
199///   u32: change id position in the sorted change ids table
200///   <commit id length number of bytes>: commit id
201/// for each entry, sorted by commit id:
202///   u32: local position in the graph entries table
203/// for each entry, sorted by change id:
204///   <change id length number of bytes>: change id
205/// for each entry, sorted by change id:
206///   if number of associated commits == 1:
207///     u32: (< 0x8000_0000) local position in the graph entries table
208///   else:
209///     u32: (>=0x8000_0000) position in the overflow table, bit-negated
210/// for each overflow parent:
211///   u32: global index position
212/// for each overflow change id entry:
213///   u32: local position in the graph entries table
214/// ```
215///
216/// Note that u32 fields are 4-byte aligned so long as the parent file name
217/// (which is hexadecimal hash) and commit/change ids aren't of exotic length.
218// TODO: replace the table by a trie so we don't have to repeat the full commit
219//       ids
220// TODO: add a fanout table like git's commit graph has?
221pub(super) struct ReadonlyIndexSegment {
222    parent_file: Option<Arc<ReadonlyIndexSegment>>,
223    num_parent_commits: u32,
224    name: String,
225    commit_id_length: usize,
226    change_id_length: usize,
227    // Number of commits not counting the parent file
228    num_local_commits: u32,
229    num_local_change_ids: u32,
230    num_change_overflow_entries: u32,
231    // Base data offsets in bytes:
232    commit_lookup_base: usize,
233    change_id_table_base: usize,
234    change_pos_table_base: usize,
235    parent_overflow_base: usize,
236    change_overflow_base: usize,
237    data: Vec<u8>,
238}
239
240impl Debug for ReadonlyIndexSegment {
241    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
242        f.debug_struct("ReadonlyIndexSegment")
243            .field("name", &self.name)
244            .field("parent_file", &self.parent_file)
245            .finish()
246    }
247}
248
249impl ReadonlyIndexSegment {
250    /// Loads both parent segments and local entries from the given file `name`.
251    pub(super) fn load(
252        dir: &Path,
253        name: String,
254        commit_id_length: usize,
255        change_id_length: usize,
256    ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
257        let mut file = File::open(dir.join(&name))
258            .map_err(|err| ReadonlyIndexLoadError::from_io_err(&name, err))?;
259        Self::load_from(&mut file, dir, name, commit_id_length, change_id_length)
260    }
261
262    /// Loads both parent segments and local entries from the given `file`.
263    pub(super) fn load_from(
264        file: &mut dyn Read,
265        dir: &Path,
266        name: String,
267        commit_id_length: usize,
268        change_id_length: usize,
269    ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
270        let from_io_err = |err| ReadonlyIndexLoadError::from_io_err(&name, err);
271        let read_u32 = |file: &mut dyn Read| {
272            let mut buf = [0; 4];
273            file.read_exact(&mut buf).map_err(from_io_err)?;
274            Ok(u32::from_le_bytes(buf))
275        };
276        let format_version = read_u32(file)?;
277        if format_version != INDEX_SEGMENT_FILE_FORMAT_VERSION {
278            return Err(ReadonlyIndexLoadError::UnexpectedVersion {
279                found_version: format_version,
280                expected_version: INDEX_SEGMENT_FILE_FORMAT_VERSION,
281            });
282        }
283        let parent_filename_len = read_u32(file)?;
284        let maybe_parent_file = if parent_filename_len > 0 {
285            let mut parent_filename_bytes = vec![0; parent_filename_len as usize];
286            file.read_exact(&mut parent_filename_bytes)
287                .map_err(from_io_err)?;
288            let parent_filename = String::from_utf8(parent_filename_bytes).map_err(|_| {
289                ReadonlyIndexLoadError::invalid_data(&name, "parent file name is not valid UTF-8")
290            })?;
291            let parent_file = ReadonlyIndexSegment::load(
292                dir,
293                parent_filename,
294                commit_id_length,
295                change_id_length,
296            )?;
297            Some(parent_file)
298        } else {
299            None
300        };
301        Self::load_with_parent_file(
302            file,
303            name,
304            maybe_parent_file,
305            commit_id_length,
306            change_id_length,
307        )
308    }
309
310    /// Loads local entries from the given `file`, returns new segment linked to
311    /// the given `parent_file`.
312    pub(super) fn load_with_parent_file(
313        file: &mut dyn Read,
314        name: String,
315        parent_file: Option<Arc<ReadonlyIndexSegment>>,
316        commit_id_length: usize,
317        change_id_length: usize,
318    ) -> Result<Arc<ReadonlyIndexSegment>, ReadonlyIndexLoadError> {
319        let from_io_err = |err| ReadonlyIndexLoadError::from_io_err(&name, err);
320        let read_u32 = |file: &mut dyn Read| {
321            let mut buf = [0; 4];
322            file.read_exact(&mut buf).map_err(from_io_err)?;
323            Ok(u32::from_le_bytes(buf))
324        };
325        let num_parent_commits = parent_file
326            .as_ref()
327            .map_or(0, |segment| segment.as_composite().num_commits());
328        let num_local_commits = read_u32(file)?;
329        let num_local_change_ids = read_u32(file)?;
330        let num_parent_overflow_entries = read_u32(file)?;
331        let num_change_overflow_entries = read_u32(file)?;
332        let mut data = vec![];
333        file.read_to_end(&mut data).map_err(from_io_err)?;
334
335        let commit_graph_entry_size = CommitGraphEntry::size(commit_id_length);
336        let graph_size = (num_local_commits as usize) * commit_graph_entry_size;
337        let commit_lookup_size = (num_local_commits as usize) * 4;
338        let change_id_table_size = (num_local_change_ids as usize) * change_id_length;
339        let change_pos_table_size = (num_local_change_ids as usize) * 4;
340        let parent_overflow_size = (num_parent_overflow_entries as usize) * 4;
341        let change_overflow_size = (num_change_overflow_entries as usize) * 4;
342
343        let graph_base = 0;
344        let commit_lookup_base = graph_base + graph_size;
345        let change_id_table_base = commit_lookup_base + commit_lookup_size;
346        let change_pos_table_base = change_id_table_base + change_id_table_size;
347        let parent_overflow_base = change_pos_table_base + change_pos_table_size;
348        let change_overflow_base = parent_overflow_base + parent_overflow_size;
349        let expected_size = change_overflow_base + change_overflow_size;
350
351        if data.len() != expected_size {
352            return Err(ReadonlyIndexLoadError::invalid_data(
353                name,
354                "unexpected data length",
355            ));
356        }
357
358        Ok(Arc::new(ReadonlyIndexSegment {
359            parent_file,
360            num_parent_commits,
361            name,
362            commit_id_length,
363            change_id_length,
364            num_local_commits,
365            num_local_change_ids,
366            num_change_overflow_entries,
367            commit_lookup_base,
368            change_id_table_base,
369            change_pos_table_base,
370            parent_overflow_base,
371            change_overflow_base,
372            data,
373        }))
374    }
375
376    pub(super) fn as_composite(&self) -> &CompositeIndex {
377        CompositeIndex::new(self)
378    }
379
380    pub(super) fn name(&self) -> &str {
381        &self.name
382    }
383
384    pub(super) fn commit_id_length(&self) -> usize {
385        self.commit_id_length
386    }
387
388    pub(super) fn change_id_length(&self) -> usize {
389        self.change_id_length
390    }
391
392    fn graph_entry(&self, local_pos: LocalPosition) -> CommitGraphEntry {
393        let table = &self.data[..self.commit_lookup_base];
394        let entry_size = CommitGraphEntry::size(self.commit_id_length);
395        let offset = (local_pos.0 as usize) * entry_size;
396        CommitGraphEntry {
397            data: &table[offset..][..entry_size],
398        }
399    }
400
401    fn commit_lookup_pos(&self, lookup_pos: u32) -> LocalPosition {
402        let table = &self.data[self.commit_lookup_base..self.change_id_table_base];
403        let offset = (lookup_pos as usize) * 4;
404        LocalPosition(u32::from_le_bytes(table[offset..][..4].try_into().unwrap()))
405    }
406
407    fn change_lookup_id(&self, lookup_pos: u32) -> ChangeId {
408        ChangeId::from_bytes(self.change_lookup_id_bytes(lookup_pos))
409    }
410
411    // might be better to add borrowed version of ChangeId
412    fn change_lookup_id_bytes(&self, lookup_pos: u32) -> &[u8] {
413        let table = &self.data[self.change_id_table_base..self.change_pos_table_base];
414        let offset = (lookup_pos as usize) * self.change_id_length;
415        &table[offset..][..self.change_id_length]
416    }
417
418    fn change_lookup_pos(&self, lookup_pos: u32) -> ChangeLocalPosition {
419        let table = &self.data[self.change_pos_table_base..self.parent_overflow_base];
420        let offset = (lookup_pos as usize) * 4;
421        ChangeLocalPosition(u32::from_le_bytes(table[offset..][..4].try_into().unwrap()))
422    }
423
424    fn overflow_parents(&self, overflow_pos: u32, num_parents: u32) -> SmallIndexPositionsVec {
425        let table = &self.data[self.parent_overflow_base..self.change_overflow_base];
426        let offset = (overflow_pos as usize) * 4;
427        let size = (num_parents as usize) * 4;
428        table[offset..][..size]
429            .chunks_exact(4)
430            .map(|chunk| IndexPosition(u32::from_le_bytes(chunk.try_into().unwrap())))
431            .collect()
432    }
433
434    /// Scans graph entry positions stored in the overflow change ids table.
435    fn overflow_changes_from(
436        &self,
437        overflow_pos: u32,
438    ) -> impl Iterator<Item = LocalPosition> + use<'_> {
439        let table = &self.data[self.change_overflow_base..];
440        let offset = (overflow_pos as usize) * 4;
441        table[offset..]
442            .chunks_exact(4)
443            .map(|chunk| LocalPosition(u32::from_le_bytes(chunk.try_into().unwrap())))
444    }
445
446    /// Binary searches commit id by `prefix`. Returns the lookup position.
447    fn commit_id_byte_prefix_to_lookup_pos(&self, prefix: &[u8]) -> PositionLookupResult {
448        binary_search_pos_by(self.num_local_commits, |pos| {
449            let local_pos = self.commit_lookup_pos(pos);
450            let entry = self.graph_entry(local_pos);
451            entry.commit_id_bytes().cmp(prefix)
452        })
453    }
454
455    /// Binary searches change id by `prefix`. Returns the lookup position.
456    fn change_id_byte_prefix_to_lookup_pos(&self, prefix: &[u8]) -> PositionLookupResult {
457        binary_search_pos_by(self.num_local_change_ids, |pos| {
458            let change_id_bytes = self.change_lookup_id_bytes(pos);
459            change_id_bytes.cmp(prefix)
460        })
461    }
462}
463
464impl IndexSegment for ReadonlyIndexSegment {
465    fn num_parent_commits(&self) -> u32 {
466        self.num_parent_commits
467    }
468
469    fn num_local_commits(&self) -> u32 {
470        self.num_local_commits
471    }
472
473    fn parent_file(&self) -> Option<&Arc<ReadonlyIndexSegment>> {
474        self.parent_file.as_ref()
475    }
476
477    fn name(&self) -> Option<String> {
478        Some(self.name.clone())
479    }
480
481    fn commit_id_to_pos(&self, commit_id: &CommitId) -> Option<LocalPosition> {
482        self.commit_id_byte_prefix_to_lookup_pos(commit_id.as_bytes())
483            .ok()
484            .map(|pos| self.commit_lookup_pos(pos))
485    }
486
487    fn resolve_neighbor_commit_ids(
488        &self,
489        commit_id: &CommitId,
490    ) -> (Option<CommitId>, Option<CommitId>) {
491        self.commit_id_byte_prefix_to_lookup_pos(commit_id.as_bytes())
492            .map_neighbors(|pos| {
493                let local_pos = self.commit_lookup_pos(pos);
494                let entry = self.graph_entry(local_pos);
495                entry.commit_id()
496            })
497    }
498
499    fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> {
500        self.commit_id_byte_prefix_to_lookup_pos(prefix.min_prefix_bytes())
501            .prefix_matches(prefix, |pos| {
502                let local_pos = self.commit_lookup_pos(pos);
503                let entry = self.graph_entry(local_pos);
504                entry.commit_id()
505            })
506            .map(|(id, _)| id)
507    }
508
509    fn resolve_neighbor_change_ids(
510        &self,
511        change_id: &ChangeId,
512    ) -> (Option<ChangeId>, Option<ChangeId>) {
513        self.change_id_byte_prefix_to_lookup_pos(change_id.as_bytes())
514            .map_neighbors(|pos| self.change_lookup_id(pos))
515    }
516
517    fn resolve_change_id_prefix(
518        &self,
519        prefix: &HexPrefix,
520    ) -> PrefixResolution<(ChangeId, SmallLocalPositionsVec)> {
521        self.change_id_byte_prefix_to_lookup_pos(prefix.min_prefix_bytes())
522            .prefix_matches(prefix, |pos| self.change_lookup_id(pos))
523            .map(|(id, lookup_pos)| {
524                let change_pos = self.change_lookup_pos(lookup_pos);
525                if let Some(local_pos) = change_pos.as_inlined() {
526                    (id, smallvec![local_pos])
527                } else {
528                    let overflow_pos = change_pos.as_overflow().unwrap();
529                    // Collect commits having the same change id. For cache
530                    // locality, it might be better to look for the next few
531                    // change id positions to determine the size.
532                    let positions: SmallLocalPositionsVec = self
533                        .overflow_changes_from(overflow_pos)
534                        .take_while(|&local_pos| {
535                            let entry = self.graph_entry(local_pos);
536                            entry.change_id_lookup_pos() == lookup_pos
537                        })
538                        .collect();
539                    debug_assert_eq!(
540                        overflow_pos + u32::try_from(positions.len()).unwrap(),
541                        (lookup_pos + 1..self.num_local_change_ids)
542                            .find_map(|lookup_pos| self.change_lookup_pos(lookup_pos).as_overflow())
543                            .unwrap_or(self.num_change_overflow_entries),
544                        "all overflow positions to the next change id should be collected"
545                    );
546                    (id, positions)
547                }
548            })
549    }
550
551    fn generation_number(&self, local_pos: LocalPosition) -> u32 {
552        self.graph_entry(local_pos).generation_number()
553    }
554
555    fn commit_id(&self, local_pos: LocalPosition) -> CommitId {
556        self.graph_entry(local_pos).commit_id()
557    }
558
559    fn change_id(&self, local_pos: LocalPosition) -> ChangeId {
560        let entry = self.graph_entry(local_pos);
561        self.change_lookup_id(entry.change_id_lookup_pos())
562    }
563
564    fn num_parents(&self, local_pos: LocalPosition) -> u32 {
565        let graph_entry = self.graph_entry(local_pos);
566        let pos1_or_overflow_pos = graph_entry.parent1_pos_or_overflow_pos();
567        let pos2_or_overflow_len = graph_entry.parent2_pos_or_overflow_len();
568        let inlined_len1 = pos1_or_overflow_pos.as_inlined().is_some() as u32;
569        let inlined_len2 = pos2_or_overflow_len.as_inlined().is_some() as u32;
570        let overflow_len = pos2_or_overflow_len.as_overflow().unwrap_or(0);
571        inlined_len1 + inlined_len2 + overflow_len
572    }
573
574    fn parent_positions(&self, local_pos: LocalPosition) -> SmallIndexPositionsVec {
575        let graph_entry = self.graph_entry(local_pos);
576        let pos1_or_overflow_pos = graph_entry.parent1_pos_or_overflow_pos();
577        let pos2_or_overflow_len = graph_entry.parent2_pos_or_overflow_len();
578        if let Some(pos1) = pos1_or_overflow_pos.as_inlined() {
579            if let Some(pos2) = pos2_or_overflow_len.as_inlined() {
580                smallvec![pos1, pos2]
581            } else {
582                smallvec![pos1]
583            }
584        } else {
585            let overflow_pos = pos1_or_overflow_pos.as_overflow().unwrap();
586            let num_parents = pos2_or_overflow_len.as_overflow().unwrap();
587            self.overflow_parents(overflow_pos, num_parents)
588        }
589    }
590}
591
592/// Commit index backend which stores data on local disk.
593#[derive(Clone, Debug)]
594pub struct DefaultReadonlyIndex(Arc<ReadonlyIndexSegment>);
595
596impl DefaultReadonlyIndex {
597    pub(super) fn from_segment(segment: Arc<ReadonlyIndexSegment>) -> Self {
598        DefaultReadonlyIndex(segment)
599    }
600
601    pub(super) fn as_segment(&self) -> &Arc<ReadonlyIndexSegment> {
602        &self.0
603    }
604}
605
606impl AsCompositeIndex for DefaultReadonlyIndex {
607    fn as_composite(&self) -> &CompositeIndex {
608        self.0.as_composite()
609    }
610}
611
612impl Index for DefaultReadonlyIndex {
613    fn shortest_unique_commit_id_prefix_len(&self, commit_id: &CommitId) -> usize {
614        self.as_composite()
615            .shortest_unique_commit_id_prefix_len(commit_id)
616    }
617
618    fn resolve_commit_id_prefix(&self, prefix: &HexPrefix) -> PrefixResolution<CommitId> {
619        self.as_composite().resolve_commit_id_prefix(prefix)
620    }
621
622    fn has_id(&self, commit_id: &CommitId) -> bool {
623        self.as_composite().has_id(commit_id)
624    }
625
626    fn is_ancestor(&self, ancestor_id: &CommitId, descendant_id: &CommitId) -> bool {
627        self.as_composite().is_ancestor(ancestor_id, descendant_id)
628    }
629
630    fn common_ancestors(&self, set1: &[CommitId], set2: &[CommitId]) -> Vec<CommitId> {
631        self.as_composite().common_ancestors(set1, set2)
632    }
633
634    fn all_heads_for_gc(
635        &self,
636    ) -> Result<Box<dyn Iterator<Item = CommitId> + '_>, AllHeadsForGcUnsupported> {
637        Ok(Box::new(self.as_composite().all_heads()))
638    }
639
640    fn heads(
641        &self,
642        candidates: &mut dyn Iterator<Item = &CommitId>,
643    ) -> Result<Vec<CommitId>, IndexError> {
644        self.as_composite().heads(candidates)
645    }
646
647    fn evaluate_revset<'index>(
648        &'index self,
649        expression: &ResolvedExpression,
650        store: &Arc<Store>,
651    ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
652        self.as_composite().evaluate_revset(expression, store)
653    }
654}
655
656impl ReadonlyIndex for DefaultReadonlyIndex {
657    fn as_any(&self) -> &dyn Any {
658        self
659    }
660
661    fn as_index(&self) -> &dyn Index {
662        self
663    }
664
665    fn change_id_index(
666        &self,
667        heads: &mut dyn Iterator<Item = &CommitId>,
668    ) -> Box<dyn ChangeIdIndex> {
669        Box::new(ChangeIdIndexImpl::new(self.clone(), heads))
670    }
671
672    fn start_modification(&self) -> Box<dyn MutableIndex> {
673        Box::new(DefaultMutableIndex::incremental(self.0.clone()))
674    }
675}
676
677/// Binary search result in a sorted lookup table.
678#[derive(Clone, Copy, Debug)]
679struct PositionLookupResult {
680    /// `Ok` means the element is found at the position. `Err` contains the
681    /// position where the element could be inserted.
682    result: Result<u32, u32>,
683    size: u32,
684}
685
686impl PositionLookupResult {
687    /// Returns position of the element if exactly matched.
688    fn ok(self) -> Option<u32> {
689        self.result.ok()
690    }
691
692    /// Returns `(previous, next)` positions of the matching element or
693    /// boundary.
694    fn neighbors(self) -> (Option<u32>, Option<u32>) {
695        match self.result {
696            Ok(pos) => (pos.checked_sub(1), (pos + 1..self.size).next()),
697            Err(pos) => (pos.checked_sub(1), (pos..self.size).next()),
698        }
699    }
700
701    /// Looks up `(previous, next)` elements by the given function.
702    fn map_neighbors<T>(self, mut lookup: impl FnMut(u32) -> T) -> (Option<T>, Option<T>) {
703        let (prev_pos, next_pos) = self.neighbors();
704        (prev_pos.map(&mut lookup), next_pos.map(&mut lookup))
705    }
706
707    /// Looks up matching elements from the current position, returns one if
708    /// the given `prefix` unambiguously matches.
709    fn prefix_matches<T: ObjectId>(
710        self,
711        prefix: &HexPrefix,
712        lookup: impl FnMut(u32) -> T,
713    ) -> PrefixResolution<(T, u32)> {
714        let lookup_pos = self.result.unwrap_or_else(|pos| pos);
715        let mut matches = (lookup_pos..self.size)
716            .map(lookup)
717            .take_while(|id| prefix.matches(id))
718            .fuse();
719        match (matches.next(), matches.next()) {
720            (Some(id), None) => PrefixResolution::SingleMatch((id, lookup_pos)),
721            (Some(_), Some(_)) => PrefixResolution::AmbiguousMatch,
722            (None, _) => PrefixResolution::NoMatch,
723        }
724    }
725}
726
727/// Binary searches u32 position with the given comparison function.
728fn binary_search_pos_by(size: u32, mut f: impl FnMut(u32) -> Ordering) -> PositionLookupResult {
729    let mut low = 0;
730    let mut high = size;
731    while low < high {
732        let mid = (low + high) / 2;
733        let cmp = f(mid);
734        // According to Rust std lib, this produces cmov instructions.
735        // https://github.com/rust-lang/rust/blob/1.76.0/library/core/src/slice/mod.rs#L2845-L2855
736        low = if cmp == Ordering::Less { mid + 1 } else { low };
737        high = if cmp == Ordering::Greater { mid } else { high };
738        if cmp == Ordering::Equal {
739            let result = Ok(mid);
740            return PositionLookupResult { result, size };
741        }
742    }
743    let result = Err(low);
744    PositionLookupResult { result, size }
745}