Skip to main content

gix_commitgraph/
init.rs

1use crate::{File, Graph, MAX_COMMITS};
2use gix_error::Result;
3use gix_error::{OptionExt, ResultExt, bail, message, validation};
4use std::{
5    io::{BufRead, BufReader},
6    path::Path,
7};
8
9/// Instantiate a `Graph` from various sources.
10impl Graph {
11    /// Instantiate a commit graph from `path` which may be a directory containing graph files or the graph file itself.
12    ///
13    /// Filesystem errors retain their [`std::io::Error`] source, including when `path` does not exist.
14    pub fn at(path: &Path) -> Result<Self> {
15        Self::try_from(path)
16    }
17
18    /// Instantiate a commit graph from the directory containing all of its files.
19    pub fn from_commit_graphs_dir(path: &Path) -> Result<Self> {
20        let commit_graphs_dir = path;
21        let chain_file_path = commit_graphs_dir.join("commit-graph-chain");
22        let chain_file = std::fs::File::open(&chain_file_path).or_raise(|| {
23            message!(
24                "Could not open commit-graph chain file at \"{}\"",
25                chain_file_path.display()
26            )
27        })?;
28        let mut files = Vec::new();
29        for line in BufReader::new(chain_file).lines() {
30            let hash = line.or_raise(|| {
31                message!(
32                    "Could not read from commit-graph file at \"{}\"",
33                    chain_file_path.display()
34                )
35            })?;
36            let graph_file_path = commit_graphs_dir.join(format!("graph-{hash}.graph"));
37            files.push(File::at(&graph_file_path)?);
38        }
39        Self::new(files).or_raise(|| {
40            message!(
41                "Could not assemble commit-graph from chain file at \"{}\"",
42                chain_file_path.display()
43            )
44            .corrupted()
45        })
46    }
47
48    /// Instantiate a commit graph from a `.git/objects/info/commit-graph` or
49    /// `.git/objects/info/commit-graphs/graph-*.graph` file.
50    pub fn from_file(path: &Path) -> Result<Self> {
51        let file = File::at(path)?;
52        Self::new(vec![file])
53            .or_raise(|| message!("Could not assemble commit-graph from file at \"{}\"", path.display()).corrupted())
54    }
55
56    /// Instantiate a commit graph from an `.git/objects/info` directory.
57    pub fn from_info_dir(info_dir: &Path) -> Result<Self> {
58        Self::from_file(&info_dir.join("commit-graph"))
59            .or_else(|_| Self::from_commit_graphs_dir(&info_dir.join("commit-graphs")))
60    }
61
62    /// Create a new commit graph from a list of `files`.
63    ///
64    /// Invalid file combinations are classified as [`gix_error::Class::Validation`].
65    pub fn new(files: Vec<File>) -> Result<Self> {
66        let files = nonempty::NonEmpty::from_vec(files)
67            .ok_or_raise(|| validation("Commit-graph must contain at least one file"))?;
68        let num_commits: u64 = files.iter().map(|f| u64::from(f.num_commits())).sum();
69        if num_commits > u64::from(MAX_COMMITS) {
70            bail!(
71                "Commit-graph files contain {num_commits} commits altogether, but only {MAX_COMMITS} commits are allowed".validation()
72            );
73        }
74
75        let mut f1 = files.first();
76        for f2 in files.tail() {
77            if f1.object_hash() != f2.object_hash() {
78                bail!(
79                    "Commit-graph files mismatch: \"{path1}\" uses hash {hash1:?}, but \"{path2}\" uses hash {hash2:?}"
80                        .validation(),
81                    path1 = f1.path().display(),
82                    hash1 = f1.object_hash(),
83                    path2 = f2.path().display(),
84                    hash2 = f2.object_hash(),
85                );
86            }
87            f1 = f2;
88        }
89
90        Ok(Self { files })
91    }
92}
93
94impl TryFrom<&Path> for Graph {
95    type Error = gix_error::Error;
96
97    fn try_from(path: &Path) -> Result<Self> {
98        let metadata = path
99            .metadata()
100            .or_raise(|| message!("Could not access commit-graph path at \"{}\"", path.display()))?;
101        if metadata.is_file() {
102            // Assume we are looking at `.git/objects/info/commit-graph` or
103            // `.git/objects/info/commit-graphs/graph-*.graph`.
104            Self::from_file(path)
105        } else if metadata.is_dir() {
106            if path.join("commit-graph-chain").is_file() {
107                Self::from_commit_graphs_dir(path)
108            } else {
109                Self::from_info_dir(path)
110            }
111        } else {
112            Err(message!(
113                "Did not find any files that look like commit graphs at \"{}\"",
114                path.display()
115            )
116            .validation_error())
117        }
118    }
119}