1use crate::{File, Graph, MAX_COMMITS};
2use gix_error::ExnMessageResult;
3use gix_error::{ErrorExt, Exn, Message, ResultExt, message};
4use std::{
5 io::{BufRead, BufReader},
6 path::Path,
7};
8
9impl Graph {
11 pub fn at(path: &Path) -> ExnMessageResult<Self> {
15 Self::try_from(path)
16 }
17
18 pub fn from_commit_graphs_dir(path: &Path) -> ExnMessageResult<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(
38 File::at(&graph_file_path)
39 .or_raise(|| message!("Could not open commit-graph file at '{}'", graph_file_path.display()))?,
40 );
41 }
42 Self::new(files)
43 }
44
45 pub fn from_file(path: &Path) -> ExnMessageResult<Self> {
48 let file = File::at(path).or_raise(|| message!("Could not open commit-graph file at '{}'", path.display()))?;
49 Self::new(vec![file])
50 }
51
52 pub fn from_info_dir(info_dir: &Path) -> ExnMessageResult<Self> {
54 Self::from_file(&info_dir.join("commit-graph"))
55 .or_else(|_| Self::from_commit_graphs_dir(&info_dir.join("commit-graphs")))
56 }
57
58 pub fn new(files: Vec<File>) -> ExnMessageResult<Self> {
60 let files = nonempty::NonEmpty::from_vec(files)
61 .ok_or_else(|| message!("Commit-graph must contain at least one file"))?;
62 let num_commits: u64 = files.iter().map(|f| u64::from(f.num_commits())).sum();
63 if num_commits > u64::from(MAX_COMMITS) {
64 return Err(message!(
65 "Commit-graph files contain {num_commits} commits altogether, but only {MAX_COMMITS} commits are allowed"
66 ).into());
67 }
68
69 let mut f1 = files.first();
70 for f2 in files.tail() {
71 if f1.object_hash() != f2.object_hash() {
72 return Err(message!(
73 "Commit-graph files mismatch: '{path1}' uses hash {hash1:?}, but '{path2}' uses hash {hash2:?}",
74 path1 = f1.path().display(),
75 hash1 = f1.object_hash(),
76 path2 = f2.path().display(),
77 hash2 = f2.object_hash(),
78 )
79 .into());
80 }
81 f1 = f2;
82 }
83
84 Ok(Self { files })
85 }
86}
87
88impl TryFrom<&Path> for Graph {
89 type Error = Exn<Message>;
90
91 fn try_from(path: &Path) -> std::result::Result<Self, Self::Error> {
92 let metadata = path
93 .metadata()
94 .or_raise(|| message!("Could not access commit-graph path at '{}'", path.display()))?;
95 if metadata.is_file() {
96 Self::from_file(path)
99 } else if metadata.is_dir() {
100 if path.join("commit-graph-chain").is_file() {
101 Self::from_commit_graphs_dir(path)
102 } else {
103 Self::from_info_dir(path)
104 }
105 } else {
106 Err(message!(
107 "Did not find any files that look like commit graphs at '{}'",
108 path.display()
109 )
110 .raise())
111 }
112 }
113}