diff options
Diffstat (limited to 'vendor/gix-odb/src/store_impls/dynamic/prefix.rs')
-rw-r--r-- | vendor/gix-odb/src/store_impls/dynamic/prefix.rs | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/vendor/gix-odb/src/store_impls/dynamic/prefix.rs b/vendor/gix-odb/src/store_impls/dynamic/prefix.rs new file mode 100644 index 000000000..9097c8cf6 --- /dev/null +++ b/vendor/gix-odb/src/store_impls/dynamic/prefix.rs @@ -0,0 +1,196 @@ +use std::{collections::HashSet, ops::Deref}; + +use crate::{ + store::{load_index, Handle}, + Find, +}; + +/// +pub mod lookup { + use crate::loose; + + /// Returned by [`Handle::lookup_prefix()`][crate::store::Handle::lookup_prefix()] + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error("An error occurred looking up a prefix which requires iteration")] + LooseWalkDir(#[from] loose::iter::Error), + #[error(transparent)] + LoadIndex(#[from] crate::store::load_index::Error), + } + + /// A way to indicate if a lookup, despite successful, was ambiguous or yielded exactly + /// one result in the particular index. + pub type Outcome = Result<gix_hash::ObjectId, ()>; +} + +/// +pub mod disambiguate { + /// A potentially ambiguous prefix for use with `Handle::disambiguate_prefix()`. + #[derive(Debug, Copy, Clone)] + pub struct Candidate { + id: gix_hash::ObjectId, + hex_len: usize, + } + + impl Candidate { + /// Create a new potentially ambiguous prefix from an `id` and the desired minimal `hex_len`. + /// + /// It is considered ambiguous until it's disambiguated by validating that there is only a single object + /// matching this prefix. + pub fn new(id: impl Into<gix_hash::ObjectId>, hex_len: usize) -> Result<Self, gix_hash::prefix::Error> { + let id = id.into(); + gix_hash::Prefix::new(id, hex_len)?; + Ok(Candidate { id, hex_len }) + } + + /// Transform ourselves into a `Prefix` with our current hex lengths. + pub fn to_prefix(&self) -> gix_hash::Prefix { + gix_hash::Prefix::new(self.id, self.hex_len).expect("our hex-len to always be in bounds") + } + + pub(crate) fn inc_hex_len(&mut self) { + self.hex_len += 1; + assert!(self.hex_len <= self.id.kind().len_in_hex()); + } + + pub(crate) fn id(&self) -> &gix_hash::oid { + &self.id + } + + pub(crate) fn hex_len(&self) -> usize { + self.hex_len + } + } + + /// Returned by [`Handle::disambiguate_prefix()`][crate::store::Handle::disambiguate_prefix()] + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error("An error occurred while trying to determine if a full hash contained in the object database")] + Contains(#[from] crate::store::find::Error), + #[error(transparent)] + Lookup(#[from] super::lookup::Error), + } +} + +impl<S> Handle<S> +where + S: Deref<Target = super::Store> + Clone, +{ + /// Return the exact number of packed objects after loading all currently available indices + /// as last seen on disk. + pub fn packed_object_count(&self) -> Result<u64, load_index::Error> { + let mut count = self.packed_object_count.borrow_mut(); + match *count { + Some(count) => Ok(count), + None => { + let mut snapshot = self.snapshot.borrow_mut(); + *snapshot = self.store.load_all_indices()?; + let mut obj_count = 0; + for index in &snapshot.indices { + obj_count += index.num_objects() as u64; + } + *count = Some(obj_count); + Ok(obj_count) + } + } + } + + /// Given a prefix `candidate` with an object id and an initial `hex_len`, check if it only matches a single + /// object within the entire object database and increment its `hex_len` by one until it is unambiguous. + /// Return `Ok(None)` if no object with that prefix exists. + pub fn disambiguate_prefix( + &self, + mut candidate: disambiguate::Candidate, + ) -> Result<Option<gix_hash::Prefix>, disambiguate::Error> { + let max_hex_len = candidate.id().kind().len_in_hex(); + if candidate.hex_len() == max_hex_len { + return Ok(self.contains(candidate.id()).then(|| candidate.to_prefix())); + } + + while candidate.hex_len() != max_hex_len { + let res = self.lookup_prefix(candidate.to_prefix(), None)?; + match res { + Some(Ok(_id)) => return Ok(Some(candidate.to_prefix())), + Some(Err(())) => { + candidate.inc_hex_len(); + continue; + } + None => return Ok(None), + } + } + Ok(Some(candidate.to_prefix())) + } + + /// Find the only object matching `prefix` and return it as `Ok(Some(Ok(<ObjectId>)))`, or return `Ok(Some(Err(()))` + /// if multiple different objects with the same prefix were found. + /// + /// Return `Ok(None)` if no object matched the `prefix`. + /// + /// Pass `candidates` to obtain the set of all object ids matching `prefix`, with the same return value as + /// one would have received if it remained `None`. + /// + /// ### Performance Note + /// + /// - Unless the handles refresh mode is set to `Never`, each lookup will trigger a refresh of the object databases files + /// on disk if the prefix doesn't lead to ambiguous results. + /// - Since all objects need to be examined to assure non-ambiguous return values, after calling this method all indices will + /// be loaded. + /// - If `candidates` is `Some(…)`, the traversal will continue to obtain all candidates, which takes more time + /// as there is no early abort. + pub fn lookup_prefix( + &self, + prefix: gix_hash::Prefix, + mut candidates: Option<&mut HashSet<gix_hash::ObjectId>>, + ) -> Result<Option<lookup::Outcome>, lookup::Error> { + let mut candidate: Option<gix_hash::ObjectId> = None; + loop { + let snapshot = self.snapshot.borrow(); + for index in snapshot.indices.iter() { + #[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow. + let lookup_result = index.lookup_prefix(prefix, candidates.as_deref_mut()); + if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) { + return Ok(Some(Err(()))); + } + } + + for lodb in snapshot.loose_dbs.iter() { + #[allow(clippy::needless_option_as_deref)] // needed as it's the equivalent of a reborrow. + let lookup_result = lodb.lookup_prefix(prefix, candidates.as_deref_mut())?; + if candidates.is_none() && !check_candidate(lookup_result, &mut candidate) { + return Ok(Some(Err(()))); + } + } + + match self.store.load_one_index(self.refresh, snapshot.marker)? { + Some(new_snapshot) => { + drop(snapshot); + *self.snapshot.borrow_mut() = new_snapshot; + } + None => { + return match &candidates { + Some(candidates) => match candidates.len() { + 0 => Ok(None), + 1 => Ok(candidates.iter().cloned().next().map(Ok)), + _ => Ok(Some(Err(()))), + }, + None => Ok(candidate.map(Ok)), + }; + } + } + } + + fn check_candidate(lookup_result: Option<lookup::Outcome>, candidate: &mut Option<gix_hash::ObjectId>) -> bool { + match (lookup_result, &*candidate) { + (Some(Ok(oid)), Some(candidate)) if *candidate != oid => false, + (Some(Ok(_)), Some(_)) | (None, None) | (None, Some(_)) => true, + (Some(Err(())), _) => false, + (Some(Ok(oid)), None) => { + *candidate = Some(oid); + true + } + } + } + } +} |