Sebastian Thielmake fmt511ed003 years agoHistory
196 lines · 7.7 KB · Rust
RawDownload
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<git_hash::ObjectId, ()>;} ///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<git_hash::ObjectId>, hex_len: usize) -> Result<Self, git_hash::prefix::Error> {            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<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<git_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: git_hash::Prefix,        mut candidates: Option<&mut HashSet<git_hash::ObjectId>>,    ) -> Result<Option<lookup::Outcome>, lookup::Error> {        let mut candidate: Option<git_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<git_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                }            }        }    }}