Skip to main content

weavatrix_git/commit_graph/
mod.rs

1mod bloom;
2mod format;
3
4use std::{fs, path::Path};
5
6use crate::{HashKind, ObjectId, Result, error::invalid};
7use format::Layer;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum PathBloom {
11    DefinitelyNot,
12    Maybe,
13}
14
15pub(crate) struct CommitGraph {
16    hash: HashKind,
17    layers: Vec<Layer>,
18}
19
20pub(crate) struct GraphCommit {
21    pub(crate) id: ObjectId,
22    pub(crate) tree: ObjectId,
23    pub(crate) parents: Vec<ObjectId>,
24    pub(crate) time: i64,
25}
26
27impl CommitGraph {
28    pub(crate) fn open(common_dir: &Path, hash: HashKind) -> Result<Option<Self>> {
29        let info = common_dir.join("objects").join("info");
30        let monolithic = info.join("commit-graph");
31        if monolithic.is_file() {
32            let Some(layer) = Layer::open(&monolithic, hash, 0, &[], None)? else {
33                return Ok(None);
34            };
35            return Ok(Some(Self {
36                hash,
37                layers: vec![layer],
38            }));
39        }
40        let directory = info.join("commit-graphs");
41        let chain = match fs::read_to_string(directory.join("commit-graph-chain")) {
42            Ok(chain) => chain,
43            Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
44            Err(error) => return Err(error.into()),
45        };
46        let ids = chain
47            .lines()
48            .filter(|line| !line.is_empty())
49            .map(|line| ObjectId::from_hex_for(line, hash))
50            .collect::<Result<Vec<_>>>()?;
51        if ids.is_empty() || ids.len() > 64 {
52            return Err(invalid("commit-graph chain length is invalid"));
53        }
54        let mut layers = Vec::with_capacity(ids.len());
55        let mut base_count = 0;
56        for (index, id) in ids.iter().enumerate() {
57            let path = directory.join(format!("graph-{}.graph", id.to_hex()));
58            let layer = Layer::open(&path, hash, base_count, &ids[..index], Some(*id))?
59                .ok_or_else(|| invalid("commit-graph chain hash kind mismatch"))?;
60            base_count = base_count
61                .checked_add(layer.count())
62                .ok_or_else(|| invalid("commit-graph chain count overflow"))?;
63            layers.push(layer);
64        }
65        Ok(Some(Self { hash, layers }))
66    }
67
68    pub(crate) fn find(&self, id: ObjectId) -> Result<Option<GraphCommit>> {
69        if id.kind() != self.hash {
70            return Ok(None);
71        }
72        for (layer_index, layer) in self.layers.iter().enumerate().rev() {
73            if let Some(position) = layer.find_position(id)? {
74                return self.entry(layer_index, position).map(Some);
75            }
76        }
77        Ok(None)
78    }
79
80    pub(crate) const fn layer_count(&self) -> usize {
81        self.layers.len()
82    }
83
84    pub(crate) fn changed_path(&self, id: ObjectId, path: &[u8]) -> Result<Option<PathBloom>> {
85        if path.is_empty() || path.contains(&0) {
86            return Err(invalid("Bloom query path is empty or contains NUL"));
87        }
88        for layer in self.layers.iter().rev() {
89            if let Some(position) = layer.find_position(id)? {
90                return layer.changed_path(position, path);
91            }
92        }
93        Ok(None)
94    }
95
96    fn entry(&self, layer_index: usize, position: usize) -> Result<GraphCommit> {
97        let layer = &self.layers[layer_index];
98        let raw = layer.raw_commit(position)?;
99        let parents = raw
100            .parents
101            .into_iter()
102            .map(|position| self.id_at_global(position))
103            .collect::<Result<Vec<_>>>()?;
104        Ok(GraphCommit {
105            id: layer.id(position)?,
106            tree: raw.tree,
107            parents,
108            time: raw.time,
109        })
110    }
111
112    fn id_at_global(&self, position: usize) -> Result<ObjectId> {
113        let layer = self
114            .layers
115            .iter()
116            .find(|layer| {
117                position >= layer.base_count()
118                    && position < layer.base_count().saturating_add(layer.count())
119            })
120            .ok_or_else(|| invalid("commit-graph parent is out of chain bounds"))?;
121        layer.id(position - layer.base_count())
122    }
123}