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
9impl Graph {
11 pub fn at(path: &Path) -> Result<Self> {
15 Self::try_from(path)
16 }
17
18 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 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 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 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 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}