Sebastian Thielthanks clippy9e046853 years agoHistory
399 lines · 15.8 KB · Rust
RawDownload
use std::{    cell::RefCell,    convert::{TryFrom, TryInto},    ops::Deref,    rc::Rc,    sync::{atomic::Ordering, Arc},}; use git_features::threading::OwnShared;use git_hash::oid; use crate::store::{handle, types, RefreshMode}; pub(crate) enum SingleOrMultiIndex {    Single {        index: Arc<git_pack::index::File>,        data: Option<Arc<git_pack::data::File>>,    },    Multi {        index: Arc<git_pack::multi_index::File>,        data: Vec<Option<Arc<git_pack::data::File>>>,    },} /// A utility to allow looking up pack offsets for a particular packpub(crate) enum IntraPackLookup<'a> {    Single(&'a git_pack::index::File),    /// the internal pack-id inside of a multi-index for which the lookup is supposed to be.    /// Used to prevent ref-delta OIDs to, for some reason, point to a different pack.    Multi {        index: &'a git_pack::multi_index::File,        required_pack_index: git_pack::multi_index::PackIndex,    },} impl<'a> IntraPackLookup<'a> {    pub(crate) fn pack_offset_by_id(&self, id: &oid) -> Option<git_pack::data::Offset> {        match self {            IntraPackLookup::Single(index) => index                .lookup(id)                .map(|entry_index| index.pack_offset_at_index(entry_index)),            IntraPackLookup::Multi {                index,                required_pack_index,            } => index.lookup(id).and_then(|entry_index| {                let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(entry_index);                (pack_index == *required_pack_index).then_some(pack_offset)            }),        }    }} pub struct IndexLookup {    pub(crate) file: SingleOrMultiIndex,    /// The index we were found at in the slot map    pub(crate) id: types::IndexId,} pub struct IndexForObjectInPack {    /// The internal identifier of the pack itself, which either is referred to by an index or a multi-pack index.    pub(crate) pack_id: types::PackId,    /// The offset at which the object's entry can be found    pub(crate) pack_offset: u64,} pub(crate) mod index_lookup {    use std::{collections::HashSet, sync::Arc};     use git_hash::oid;     use crate::store::{handle, handle::IntraPackLookup, types};     pub(crate) struct Outcome<'a> {        pub object_index: handle::IndexForObjectInPack,        pub index_file: IntraPackLookup<'a>,        pub pack: &'a mut Option<Arc<git_pack::data::File>>,    }     impl handle::IndexLookup {        /// Return an iterator over the entries of the given pack. The `pack_id` is required to identify a pack uniquely within        /// a potential multi-pack index.        pub(crate) fn iter(            &self,            pack_id: types::PackId,        ) -> Option<Box<dyn Iterator<Item = git_pack::index::Entry> + '_>> {            (self.id == pack_id.index).then(|| match &self.file {                handle::SingleOrMultiIndex::Single { index, .. } => index.iter(),                handle::SingleOrMultiIndex::Multi { index, .. } => {                    let pack_index = pack_id.multipack_index.expect(                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",                    );                    Box::new(index.iter().filter_map(move |e| {                        (e.pack_index == pack_index).then_some(git_pack::index::Entry {                            oid: e.oid,                            pack_offset: e.pack_offset,                            crc32: None,                        })                    }))                }            })        }         pub(crate) fn pack(&mut self, pack_id: types::PackId) -> Option<&'_ mut Option<Arc<git_pack::data::File>>> {            (self.id == pack_id.index).then(move || match &mut self.file {                handle::SingleOrMultiIndex::Single { data, .. } => data,                handle::SingleOrMultiIndex::Multi { data, .. } => {                    let pack_index = pack_id.multipack_index.expect(                        "BUG: multi-pack index must be set if this is a multi-pack, pack-indices seem unstable",                    );                    &mut data[pack_index as usize]                }            })        }         /// Return true if the given object id exists in this index        pub(crate) fn contains(&self, object_id: &oid) -> bool {            match &self.file {                handle::SingleOrMultiIndex::Single { index, .. } => index.lookup(object_id).is_some(),                handle::SingleOrMultiIndex::Multi { index, .. } => index.lookup(object_id).is_some(),            }        }         /// Return true if the given object id exists in this index        pub(crate) fn oid_at_index(&self, entry_index: u32) -> &git_hash::oid {            match &self.file {                handle::SingleOrMultiIndex::Single { index, .. } => index.oid_at_index(entry_index),                handle::SingleOrMultiIndex::Multi { index, .. } => index.oid_at_index(entry_index),            }        }         /// Return the amount of objects contained in the index, essentially the number of object ids.        pub(crate) fn num_objects(&self) -> u32 {            match &self.file {                handle::SingleOrMultiIndex::Single { index, .. } => index.num_objects(),                handle::SingleOrMultiIndex::Multi { index, .. } => index.num_objects(),            }        }         /// Call `lookup_prefix(…)` on either index or multi-index, and transform matches into an object id.        pub(crate) fn lookup_prefix(            &self,            prefix: git_hash::Prefix,            candidates: Option<&mut HashSet<git_hash::ObjectId>>,        ) -> Option<crate::store::prefix::lookup::Outcome> {            let mut candidate_entries = candidates.as_ref().map(|_| 0..0);            let res = match &self.file {                handle::SingleOrMultiIndex::Single { index, .. } => {                    index.lookup_prefix(prefix, candidate_entries.as_mut())                }                handle::SingleOrMultiIndex::Multi { index, .. } => {                    index.lookup_prefix(prefix, candidate_entries.as_mut())                }            }?;             if let Some((candidates, entries)) = candidates.zip(candidate_entries) {                candidates.extend(entries.map(|entry| self.oid_at_index(entry).to_owned()));            }            Some(res.map(|entry_index| self.oid_at_index(entry_index).to_owned()))        }         /// See if the oid is contained in this index, and return its full id for lookup possibly alongside its data file if already        /// loaded.        /// Also return the index itself as it's needed to resolve intra-pack ref-delta objects. They are a possibility even though        /// they won't be used in practice as it's more efficient to store their offsets.        /// If it is not loaded, ask it to be loaded and put it into the returned mutable option for safe-keeping.        pub(crate) fn lookup(&mut self, object_id: &oid) -> Option<Outcome<'_>> {            let id = self.id;            match &mut self.file {                handle::SingleOrMultiIndex::Single { index, data } => index.lookup(object_id).map(move |idx| Outcome {                    object_index: handle::IndexForObjectInPack {                        pack_id: types::PackId {                            index: id,                            multipack_index: None,                        },                        pack_offset: index.pack_offset_at_index(idx),                    },                    index_file: IntraPackLookup::Single(index),                    pack: data,                }),                handle::SingleOrMultiIndex::Multi { index, data } => index.lookup(object_id).map(move |idx| {                    let (pack_index, pack_offset) = index.pack_id_and_pack_offset_at_index(idx);                    Outcome {                        object_index: handle::IndexForObjectInPack {                            pack_id: types::PackId {                                index: id,                                multipack_index: Some(pack_index),                            },                            pack_offset,                        },                        index_file: IntraPackLookup::Multi {                            index,                            required_pack_index: pack_index,                        },                        pack: &mut data[pack_index as usize],                    }                }),            }        }    }} pub(crate) enum Mode {    DeletedPacksAreInaccessible,    /// This mode signals that we should not unload packs even after they went missing.    KeepDeletedPacksAvailable,} /// Handle registrationimpl super::Store {    pub(crate) fn register_handle(&self) -> Mode {        self.num_handles_unstable.fetch_add(1, Ordering::Relaxed);        Mode::DeletedPacksAreInaccessible    }    pub(crate) fn remove_handle(&self, mode: Mode) {        match mode {            Mode::KeepDeletedPacksAvailable => {                let _lock = self.write.lock();                self.num_handles_stable.fetch_sub(1, Ordering::SeqCst)            }            Mode::DeletedPacksAreInaccessible => self.num_handles_unstable.fetch_sub(1, Ordering::Relaxed),        };    }    pub(crate) fn upgrade_handle(&self, mode: Mode) -> Mode {        if let Mode::DeletedPacksAreInaccessible = mode {            let _lock = self.write.lock();            self.num_handles_stable.fetch_add(1, Ordering::SeqCst);            self.num_handles_unstable.fetch_sub(1, Ordering::SeqCst);        }        Mode::KeepDeletedPacksAvailable    }} /// Handle creationimpl super::Store {    /// The amount of times a ref-delta base can be followed when multi-indices are involved.    pub const INITIAL_MAX_RECURSION_DEPTH: usize = 32;     /// Create a new cache filled with a handle to this store, if this store is supporting shared ownership.    ///    /// Note that the actual type of `OwnShared` depends on the `parallel` feature toggle of the `git-features` crate.    pub fn to_cache(self: &OwnShared<Self>) -> crate::Cache<super::Handle<OwnShared<super::Store>>> {        self.to_handle().into()    }     /// Create a new cache filled with a handle to this store if this store is held in an `Arc`.    pub fn to_cache_arc(self: &Arc<Self>) -> crate::Cache<super::Handle<Arc<super::Store>>> {        self.to_handle_arc().into()    }     /// Create a new database handle to this store if this store is supporting shared ownership.    ///    /// See also, [`to_cache()`][super::Store::to_cache()] which is probably more useful.    pub fn to_handle(self: &OwnShared<Self>) -> super::Handle<OwnShared<super::Store>> {        let token = self.register_handle();        super::Handle {            store: self.clone(),            refresh: RefreshMode::default(),            ignore_replacements: false,            token: Some(token),            snapshot: RefCell::new(self.collect_snapshot()),            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,            packed_object_count: Default::default(),        }    }     /// Create a new database handle to this store if this store is held in an `Arc`.    ///    /// This method is useful in applications that know they will use threads.    pub fn to_handle_arc(self: &Arc<Self>) -> super::Handle<Arc<super::Store>> {        let token = self.register_handle();        super::Handle {            store: self.clone(),            refresh: Default::default(),            ignore_replacements: false,            token: Some(token),            snapshot: RefCell::new(self.collect_snapshot()),            max_recursion_depth: Self::INITIAL_MAX_RECURSION_DEPTH,            packed_object_count: Default::default(),        }    }     /// Transform the only instance into an `Arc<Self>` or panic if this is not the only Rc handle    /// to the contained store.    ///    /// This is meant to be used when the `git_features::threading::OwnShared` refers to an `Rc` as it was compiled without the    /// `parallel` feature toggle.    pub fn into_shared_arc(self: OwnShared<Self>) -> Arc<Self> {        match OwnShared::try_unwrap(self) {            Ok(this) => Arc::new(this),            Err(_) => panic!("BUG: Must be called when there is only one owner for this RC"),        }    }} impl<S> super::Handle<S>where    S: Deref<Target = super::Store> + Clone,{    /// Call once if pack ids are stored and later used for lookup, meaning they should always remain mapped and not be unloaded    /// even if they disappear from disk.    /// This must be called if there is a chance that git maintenance is happening while a pack is created.    pub fn prevent_pack_unload(&mut self) {        self.token = self.token.take().map(|token| self.store.upgrade_handle(token));    }     /// Return a shared reference to the contained store.    pub fn store_ref(&self) -> &S::Target {        &self.store    }     /// Return an owned store with shared ownership.    pub fn store(&self) -> S {        self.store.clone()    }     /// Set the handle to never cause ODB refreshes if an object could not be found.    ///    /// The latter is the default, as typically all objects referenced in a git repository are contained in the local clone.    /// More recently, however, this doesn't always have to be the case due to sparse checkouts and other ways to only have a    /// limited amount of objects available locally.    pub fn refresh_never(&mut self) {        self.refresh = RefreshMode::Never;    }     /// Return the current refresh mode.    pub fn refresh_mode(&mut self) -> RefreshMode {        self.refresh    }} impl<S> Drop for super::Handle<S>where    S: Deref<Target = super::Store> + Clone,{    fn drop(&mut self) {        if let Some(token) = self.token.take() {            self.store.remove_handle(token)        }    }} impl TryFrom<&super::Store> for super::Store {    type Error = std::io::Error;     fn try_from(s: &super::Store) -> Result<Self, Self::Error> {        super::Store::at_opts(            s.path(),            s.replacements(),            crate::store::init::Options {                slots: crate::store::init::Slots::Given(s.files.len().try_into().expect("BUG: too many slots")),                object_hash: Default::default(),                use_multi_pack_index: false,                current_dir: s.current_dir.clone().into(),            },        )    }} impl super::Handle<Rc<super::Store>> {    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store.    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {        let store = Arc::new(super::Store::try_from(self.store_ref())?);        let mut cache = store.to_handle_arc();        cache.refresh = self.refresh;        cache.max_recursion_depth = self.max_recursion_depth;        Ok(cache)    }} impl super::Handle<Arc<super::Store>> {    /// Convert a ref counted store into one that is ref-counted and thread-safe, by creating a new Store    pub fn into_arc(self) -> std::io::Result<super::Handle<Arc<super::Store>>> {        Ok(self)    }} impl<S> Clone for super::Handle<S>where    S: Deref<Target = super::Store> + Clone,{    fn clone(&self) -> Self {        super::Handle {            store: self.store.clone(),            refresh: self.refresh,            ignore_replacements: self.ignore_replacements,            token: {                let token = self.store.register_handle();                match self.token.as_ref().expect("token is always set here ") {                    handle::Mode::DeletedPacksAreInaccessible => token,                    handle::Mode::KeepDeletedPacksAvailable => self.store.upgrade_handle(token),                }                .into()            },            snapshot: RefCell::new(self.store.collect_snapshot()),            max_recursion_depth: self.max_recursion_depth,            packed_object_count: Default::default(),        }    }}