gix_odb/store_impls/dynamic/
init.rs

1use std::{path::PathBuf, sync::Arc};
2
3use arc_swap::ArcSwap;
4
5use crate::{
6    store::types::{MutableIndexAndPack, SlotMapIndex},
7    Store,
8};
9
10/// Options for use in [`Store::at_opts()`].
11#[derive(Clone, Debug)]
12pub struct Options {
13    /// How to obtain a size for the slot map.
14    pub slots: Slots,
15    /// The kind of hash we expect in our packs and would use for loose object iteration and object writing.
16    pub object_hash: gix_hash::Kind,
17    /// If false, no multi-pack indices will be used. If true, they will be used if their hash matches `object_hash`.
18    pub use_multi_pack_index: bool,
19    /// The current directory of the process at the time of instantiation.
20    /// If unset, it will be retrieved using `gix_fs::current_dir(false)`.
21    pub current_dir: Option<std::path::PathBuf>,
22}
23
24impl Default for Options {
25    fn default() -> Self {
26        Options {
27            slots: Default::default(),
28            object_hash: Default::default(),
29            use_multi_pack_index: true,
30            current_dir: None,
31        }
32    }
33}
34
35/// Configures the amount of slots in the index slotmap, which is fixed throughout the existence of the store.
36#[derive(Copy, Clone, Debug)]
37pub enum Slots {
38    /// The amount of slots to use, that is the total amount of indices we can hold at a time.
39    /// Using this has the advantage of avoiding an initial directory listing of the repository, and is recommended
40    /// on the server side where the repository setup is controlled.
41    ///
42    /// Note that this won't affect their packs, as each index can have one or more packs associated with it.
43    Given(u16),
44    /// Compute the amount of slots needed, as probably best used on the client side where a variety of repositories is encountered.
45    AsNeededByDiskState {
46        /// 1.0 means no safety, 1.1 means 10% more slots than needed
47        multiplier: f32,
48        /// The minimum amount of slots to assume
49        minimum: usize,
50    },
51}
52
53impl Default for Slots {
54    fn default() -> Self {
55        Slots::AsNeededByDiskState {
56            multiplier: 1.1,
57            minimum: 32,
58        }
59    }
60}
61
62impl Store {
63    /// Open the store at `objects_dir` (containing loose objects and `packs/`), which must only be a directory for
64    /// the store to be created without any additional work being done.
65    /// `slots` defines how many multi-pack-indices as well as indices we can know about at a time, which includes
66    /// the allowance for all additional object databases coming in via `alternates` as well.
67    /// Note that the `slots` isn't used for packs, these are included with their multi-index or index respectively.
68    /// For example, In a repository with 250m objects and geometric packing one would expect 27 index/pack pairs,
69    /// or a single multi-pack index.
70    /// `replacements` is an iterator over pairs of old and new object ids for replacement support.
71    /// This means that when asking for object `X`, one will receive object `X-replaced` given an iterator like `Some((X, X-replaced))`.
72    pub fn at_opts(
73        objects_dir: PathBuf,
74        replacements: &mut dyn Iterator<Item = (gix_hash::ObjectId, gix_hash::ObjectId)>,
75        Options {
76            slots,
77            object_hash,
78            use_multi_pack_index,
79            current_dir,
80        }: Options,
81    ) -> std::io::Result<Self> {
82        let _span = gix_features::trace::detail!("gix_odb::Store::at()");
83        let current_dir = current_dir.map_or_else(
84            || {
85                // It's only used for real-pathing alternate paths and there it just needs to be consistent (enough).
86                gix_fs::current_dir(false)
87            },
88            Ok,
89        )?;
90        if !objects_dir.is_dir() {
91            return Err(std::io::Error::new(
92                std::io::ErrorKind::Other, // TODO: use NotADirectory when stabilized
93                format!("'{}' wasn't a directory", objects_dir.display()),
94            ));
95        }
96        let slot_count = match slots {
97            Slots::Given(n) => n as usize,
98            Slots::AsNeededByDiskState { multiplier, minimum } => {
99                let mut db_paths = crate::alternate::resolve(objects_dir.clone(), &current_dir)
100                    .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
101                db_paths.insert(0, objects_dir.clone());
102                let num_slots = super::Store::collect_indices_and_mtime_sorted_by_size(db_paths, None, None)
103                    .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?
104                    .len();
105
106                ((num_slots as f32 * multiplier) as usize).max(minimum)
107            }
108        };
109        if slot_count > crate::store::types::PackId::max_indices() {
110            return Err(std::io::Error::new(
111                std::io::ErrorKind::Other,
112                "Cannot use more than 1^15 slots",
113            ));
114        }
115        let mut replacements: Vec<_> = replacements.collect();
116        replacements.sort_by(|a, b| a.0.cmp(&b.0));
117
118        Ok(Store {
119            current_dir,
120            write: Default::default(),
121            replacements,
122            path: objects_dir,
123            files: Vec::from_iter(std::iter::repeat_with(MutableIndexAndPack::default).take(slot_count)),
124            index: ArcSwap::new(Arc::new(SlotMapIndex::default())),
125            use_multi_pack_index,
126            object_hash,
127            num_handles_stable: Default::default(),
128            num_handles_unstable: Default::default(),
129            num_disk_state_consolidation: Default::default(),
130        })
131    }
132}