gix_odb/store_impls/dynamic/
prefix.rs

1use std::{collections::HashSet, ops::Deref};
2
3use gix_object::Exists;
4
5use crate::store::{load_index, Handle};
6
7///
8pub mod lookup {
9    use crate::loose;
10
11    /// Returned by [`Handle::lookup_prefix()`][crate::store::Handle::lookup_prefix()]
12    #[derive(thiserror::Error, Debug)]
13    #[allow(missing_docs)]
14    pub enum Error {
15        #[error("An error occurred looking up a prefix which requires iteration")]
16        LooseWalkDir(#[from] loose::iter::Error),
17        #[error(transparent)]
18        LoadIndex(#[from] crate::store::load_index::Error),
19    }
20
21    /// A way to indicate if a lookup, despite successful, was ambiguous or yielded exactly
22    /// one result in the particular index.
23    pub type Outcome = Result<gix_hash::ObjectId, ()>;
24}
25
26///
27pub mod disambiguate {
28    /// A potentially ambiguous prefix for use with `Handle::disambiguate_prefix()`.
29    #[derive(Debug, Copy, Clone)]
30    pub struct Candidate {
31        id: gix_hash::ObjectId,
32        hex_len: usize,
33    }
34
35    impl Candidate {
36        /// Create a new potentially ambiguous prefix from an `id` and the desired minimal `hex_len`.
37        ///
38        /// It is considered ambiguous until it's disambiguated by validating that there is only a single object
39        /// matching this prefix.
40        pub fn new(id: impl Into<gix_hash::ObjectId>, hex_len: usize) -> Result<Self, gix_hash::prefix::Error> {
41            let id = id.into();
42            gix_hash::Prefix::new(&id, hex_len)?;
43            Ok(Candidate { id, hex_len })
44        }
45
46        /// Transform ourselves into a `Prefix` with our current hex lengths.
47        pub fn to_prefix(&self) -> gix_hash::Prefix {
48            gix_hash::Prefix::new(&self.id, self.hex_len).expect("our hex-len to always be in bounds")
49        }
50
51        pub(crate) fn inc_hex_len(&mut self) {
52            self.hex_len += 1;
53            assert!(self.hex_len <= self.id.kind().len_in_hex());
54        }
55
56        pub(crate) fn id(&self) -> &gix_hash::oid {
57            &self.id
58        }
59
60        pub(crate) fn hex_len(&self) -> usize {
61            self.hex_len
62        }
63    }
64
65    /// Returned by [`Handle::disambiguate_prefix()`][crate::store::Handle::disambiguate_prefix()]
66    #[derive(thiserror::Error, Debug)]
67    #[allow(missing_docs)]
68    pub enum Error {
69        #[error("An error occurred while trying to determine if a full hash contained in the object database")]
70        Contains(#[from] crate::store::find::Error),
71        #[error(transparent)]
72        Lookup(#[from] super::lookup::Error),
73    }
74}
75
76impl<S> Handle<S>
77where
78    S: Deref<Target = super::Store> + Clone,
79{
80    /// Return the exact number of packed objects after loading all currently available indices
81    /// as last seen on disk.
82    pub fn packed_object_count(&self) -> Result<u64, load_index::Error> {
83        let mut count = self.packed_object_count.borrow_mut();
84        match *count {
85            Some(count) => Ok(count),
86            None => {
87                let _span = gix_features::trace::detail!("gix_odb::Handle::packed_object_count()");
88                let mut snapshot = self.snapshot.borrow_mut();
89                *snapshot = self.store.load_all_indices()?;
90                let mut obj_count = 0;
91                for index in &snapshot.indices {
92                    obj_count += u64::from(index.num_objects());
93                }
94                *count = Some(obj_count);
95                Ok(obj_count)
96            }
97        }
98    }
99
100    /// Given a prefix `candidate` with an object id and an initial `hex_len`, check if it only matches a single
101    /// object within the entire object database and increment its `hex_len` by one until it is unambiguous.
102    /// Return `Ok(None)` if no object with that prefix exists.
103    pub fn disambiguate_prefix(
104        &self,
105        mut candidate: disambiguate::Candidate,
106    ) -> Result<Option<gix_hash::Prefix>, disambiguate::Error> {
107        let max_hex_len = candidate.id().kind().len_in_hex();
108        if candidate.hex_len() == max_hex_len {
109            return Ok(self.exists(candidate.id()).then(|| candidate.to_prefix()));
110        }
111
112        while candidate.hex_len() != max_hex_len {
113            let res = self.lookup_prefix(candidate.to_prefix(), None)?;
114            match res {
115                Some(Ok(_id)) => return Ok(Some(candidate.to_prefix())),
116                Some(Err(())) => {
117                    candidate.inc_hex_len();
118                    continue;
119                }
120                None => return Ok(None),
121            }
122        }
123        Ok(Some(candidate.to_prefix()))
124    }
125
126    /// Find the only object matching `prefix` and return it as `Ok(Some(Ok(<ObjectId>)))`, or return `Ok(Some(Err(()))`
127    /// if multiple different objects with the same prefix were found.
128    ///
129    /// Return `Ok(None)` if no object matched the `prefix`.
130    ///
131    /// Pass `candidates` to obtain the set of all object ids matching `prefix`, with the same return value as
132    /// one would have received if it remained `None`.
133    ///
134    /// ### Performance Note
135    ///
136    /// - Unless the handles refresh mode is set to `Never`, each lookup will trigger a refresh of the object databases files
137    ///   on disk if the prefix doesn't lead to ambiguous results.
138    /// - Since all objects need to be examined to assure non-ambiguous return values, after calling this method all indices will
139    ///   be loaded.
140    /// - If `candidates` is `Some(…)`, the traversal will continue to obtain all candidates, which takes more time
141    ///   as there is no early abort.
142    pub fn lookup_prefix(
143        &self,
144        prefix: gix_hash::Prefix,
145        mut candidates: Option<&mut HashSet<gix_hash::ObjectId>>,
146    ) -> Result<Option<lookup::Outcome>, lookup::Error> {
147        let mut candidate: Option<gix_hash::ObjectId> = None;
148        loop {
149            let snapshot = self.snapshot.borrow();
150            for index in &snapshot.indices {
151                #[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow.
152                let lookup_result = index.lookup_prefix(prefix, candidates.as_deref_mut());
153                if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) {
154                    return Ok(Some(Err(())));
155                }
156            }
157
158            for lodb in snapshot.loose_dbs.iter() {
159                #[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow.
160                let lookup_result = lodb.lookup_prefix(prefix, candidates.as_deref_mut())?;
161                if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) {
162                    return Ok(Some(Err(())));
163                }
164            }
165
166            match self.store.load_one_index(self.refresh, snapshot.marker)? {
167                Some(new_snapshot) => {
168                    drop(snapshot);
169                    *self.snapshot.borrow_mut() = new_snapshot;
170                }
171                None => {
172                    return match &candidates {
173                        Some(candidates) => match candidates.len() {
174                            0 => Ok(None),
175                            1 => Ok(candidates.iter().copied().next().map(Ok)),
176                            _ => Ok(Some(Err(()))),
177                        },
178                        None => Ok(candidate.map(Ok)),
179                    };
180                }
181            }
182        }
183
184        fn check_candidate(lookup_result: Option<lookup::Outcome>, candidate: &mut Option<gix_hash::ObjectId>) -> bool {
185            match (lookup_result, &*candidate) {
186                (Some(Ok(oid)), Some(candidate)) if *candidate != oid => false,
187                (Some(Ok(_)) | None, Some(_)) | (None, None) => true,
188                (Some(Err(())), _) => false,
189                (Some(Ok(oid)), None) => {
190                    *candidate = Some(oid);
191                    true
192                }
193            }
194        }
195    }
196}