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 files = nonempty::NonEmpty::from_vec(files)
58 .ok_or_else(|| message!("Commit-graph must contain at least one file"))?;
59 let num_commits: u64 = files.iter().map(|f| u64::from(f.num_commits())).sum();
60 if num_commits > u64::from(MAX_COMMITS) {
61 return Err(message!(
62 "Commit-graph files contain {num_commits} commits altogether, but only {MAX_COMMITS} commits are allowed"
63 ));
64 }
65
66 let mut f1 = files.first();
67 for f2 in files.tail() {
68 if f1.object_hash() != f2.object_hash() {
69 return Err(message!(
70 "Commit-graph files mismatch: '{path1}' uses hash {hash1:?}, but '{path2}' uses hash {hash2:?}",
71 path1 = f1.path().display(),
72 hash1 = f1.object_hash(),
73 path2 = f2.path().display(),
74 hash2 = f2.object_hash(),
75 ));
76 }
77 f1 = f2;
78 }
79
80 Ok(Self { files })
81 }
82}
83
84impl TryFrom<&Path> for Graph {
85 type Error = Exn<Message>;
86
87 fn try_from(path: &Path) -> Result<Self, Self::Error> {
88 if path.is_file() {
89 Self::from_file(path)
92 } else if path.is_dir() {
93 if path.join("commit-graph-chain").is_file() {
94 Self::from_commit_graphs_dir(path)
95 } else {
96 Self::from_info_dir(path)
97 }
98 } else {
99 Err(message!(
100 "Did not find any files that look like commit graphs at '{}'",
101 path.display()
102 )
103 .raise())
104 }
105 }
106}