gix_commitgraph/
init.rs

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