Skip to main content

gix_pack/multi_index/
access.rs

1use std::{
2    ops::Range,
3    path::{Path, PathBuf},
4};
5
6use crate::{
7    data,
8    index::PrefixLookupResult,
9    multi_index::{EntryIndex, File, PackIndex, Version},
10};
11
12/// Represents an entry within a multi index file, effectively mapping object [`IDs`][gix_hash::ObjectId] to pack data
13/// files and the offset within.
14#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct Entry {
17    /// The ID of the object.
18    pub oid: gix_hash::ObjectId,
19    /// The offset to the object's header in the pack data file.
20    pub pack_offset: data::Offset,
21    /// The index of the pack matching our [`File::index_names()`] slice.
22    pub pack_index: PackIndex,
23}
24
25/// Access methods
26impl<T> File<T>
27where
28    T: crate::FileData,
29{
30    /// Returns the version of the multi-index file.
31    pub fn version(&self) -> Version {
32        self.version
33    }
34    /// Returns the path from which the multi-index file was loaded.
35    ///
36    /// Note that it might have changed in the mean time, or might have been removed as well.
37    pub fn path(&self) -> &Path {
38        &self.path
39    }
40    /// Returns the amount of indices stored in this multi-index file. It's the same as [File::index_names().len()][File::index_names()],
41    /// and returned as one past the highest known index.
42    pub fn num_indices(&self) -> PackIndex {
43        self.num_indices
44    }
45    /// Returns the total amount of objects available for lookup, and returned as one past the highest known entry index
46    pub fn num_objects(&self) -> EntryIndex {
47        self.num_objects
48    }
49    /// Returns the kind of hash function used for object ids available in this index.
50    pub fn object_hash(&self) -> gix_hash::Kind {
51        self.object_hash
52    }
53    /// Returns the checksum over the entire content of the file (excluding the checksum itself).
54    ///
55    /// It can be used to validate it didn't change after creation.
56    pub fn checksum(&self) -> gix_hash::ObjectId {
57        gix_hash::ObjectId::from_bytes_or_panic(&self.data[self.data.len() - self.hash_len..])
58    }
59    /// Return all names of index files (`*.idx`) whose objects we contain.
60    ///
61    /// The corresponding pack can be found by replacing the `.idx` extension with `.pack`.
62    pub fn index_names(&self) -> &[PathBuf] {
63        &self.index_names
64    }
65}
66
67impl<T> File<T>
68where
69    T: crate::FileData,
70{
71    /// Return the object id at the given `index`, which ranges from 0 to [File::num_objects()].
72    pub fn oid_at_index(&self, index: EntryIndex) -> &gix_hash::oid {
73        debug_assert!(index < self.num_objects, "index out of bounds");
74        let index: usize = index as usize;
75        let start = self.lookup_ofs + index * self.hash_len;
76        gix_hash::oid::from_bytes_unchecked(&self.data[start..][..self.hash_len])
77    }
78
79    /// Given a `prefix`, find an object that matches it uniquely within this index and return `Some(Ok(entry_index))`.
80    /// If there is more than one object matching the object `Some(Err(())` is returned.
81    ///
82    /// Finally, if no object matches the index, the return value is `None`.
83    ///
84    /// Pass `candidates` to obtain the set of entry-indices matching `prefix`, with the same return value as
85    /// one would have received if it remained `None`. It will be empty if no object matched the `prefix`.
86    ///
87    // NOTE: pretty much the same things as in `index::File::lookup`, change things there
88    //       as well.
89    pub fn lookup_prefix(
90        &self,
91        prefix: gix_hash::Prefix,
92        candidates: Option<&mut Range<EntryIndex>>,
93    ) -> Option<PrefixLookupResult> {
94        crate::index::access::lookup_prefix(
95            prefix,
96            candidates,
97            &self.fan,
98            &|idx| self.oid_at_index(idx),
99            self.num_objects,
100        )
101    }
102
103    /// Find the index ranging from 0 to [File::num_objects()] that belongs to data associated with `id`, or `None` if it wasn't found.
104    ///
105    /// Use this index for finding additional information via [`File::pack_id_and_pack_offset_at_index()`].
106    pub fn lookup(&self, id: impl AsRef<gix_hash::oid>) -> Option<EntryIndex> {
107        crate::index::access::lookup(id.as_ref(), &self.fan, &|idx| self.oid_at_index(idx))
108    }
109
110    /// Given the `index` ranging from 0 to [File::num_objects()], return the pack index and its absolute offset into the pack.
111    ///
112    /// The pack-index refers to an entry in the [`index_names`][File::index_names()] list, from which the pack can be derived.
113    pub fn pack_id_and_pack_offset_at_index(&self, index: EntryIndex) -> (PackIndex, data::Offset) {
114        const OFFSET_ENTRY_SIZE: usize = 4 + 4;
115        let index = index as usize;
116        let start = self.offsets_ofs + index * OFFSET_ENTRY_SIZE;
117
118        const HIGH_BIT: u32 = 1 << 31;
119
120        let pack_index = crate::read_u32(&self.data[start..][..4]);
121        let offset = &self.data[start + 4..][..4];
122        let ofs32 = crate::read_u32(offset);
123        let pack_offset = if (ofs32 & HIGH_BIT) == HIGH_BIT {
124            // We determine if large offsets are actually larger than 4GB and if not, we don't use the high-bit to signal anything
125            // but allow the presence of the large-offset chunk to signal what's happening.
126            if let Some(offsets_64) = self.large_offsets_ofs {
127                let from = offsets_64 + (ofs32 ^ HIGH_BIT) as usize * 8;
128                crate::read_u64(&self.data[from..][..8])
129            } else {
130                u64::from(ofs32)
131            }
132        } else {
133            u64::from(ofs32)
134        };
135        (pack_index, pack_offset)
136    }
137
138    /// Return an iterator over all entries within this file.
139    pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
140        (0..self.num_objects).map(move |idx| {
141            let (pack_index, pack_offset) = self.pack_id_and_pack_offset_at_index(idx);
142            Entry {
143                oid: self.oid_at_index(idx).to_owned(),
144                pack_offset,
145                pack_index,
146            }
147        })
148    }
149}