use crate::store::{load_index, Handle}; use crate::Find; use std::collections::HashSet; use std::ops::Deref; /// 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; } /// pub mod disambiguate { /// A potentially ambiguous prefix for use with `Handle::disambiguate_prefix()`. #[derive(Debug, Copy, Clone)] pub struct Candidate { id: git_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, hex_len: usize) -> Result { let id = id.into(); git_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) -> git_hash::Prefix { git_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) -> &git_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 Handle where S: Deref + 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 { 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, 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()))`, 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: git_hash::Prefix, mut candidates: Option<&mut HashSet>, ) -> Result, lookup::Error> { let mut candidate: Option = 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, candidate: &mut Option) -> 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 } } } } }