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
8impl Graph {
10 pub fn at(path: &Path) -> Result<Self, Exn<Message>> {
12 Self::try_from(path)
13 }
14
15 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 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 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 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 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}