git_commitgraph/graph/
init.rs1use std::{
2 convert::TryFrom,
3 io::{BufRead, BufReader},
4 path::{Path, PathBuf},
5};
6
7use crate::{
8 file::{self, File},
9 Graph, MAX_COMMITS,
10};
11
12#[derive(thiserror::Error, Debug)]
14#[allow(missing_docs)]
15pub enum Error {
16 #[error("{}", .path.display())]
17 File {
18 #[source]
19 err: file::Error,
20 path: PathBuf,
21 },
22 #[error("Commit-graph files mismatch: '{}' uses hash {hash1:?}, but '{}' uses hash {hash2:?}", .path1.display(), .path2.display())]
23 HashVersionMismatch {
24 path1: PathBuf,
25 hash1: git_hash::Kind,
26 path2: PathBuf,
27 hash2: git_hash::Kind,
28 },
29 #[error("Did not find any files that look like commit graphs at '{}'", .0.display())]
30 InvalidPath(PathBuf),
31 #[error("Could not open commit-graph file at '{}'", .path.display())]
32 Io {
33 #[source]
34 err: std::io::Error,
35 path: PathBuf,
36 },
37 #[error(
38 "Commit-graph files contain {0} commits altogether, but only {} commits are allowed",
39 MAX_COMMITS
40 )]
41 TooManyCommits(u64),
42}
43
44impl Graph {
46 pub fn at(path: impl AsRef<Path>) -> Result<Self, Error> {
48 Self::try_from(path.as_ref())
49 }
50
51 pub fn from_commit_graphs_dir(path: impl AsRef<Path>) -> Result<Self, Error> {
53 let commit_graphs_dir = path.as_ref();
54 let chain_file_path = commit_graphs_dir.join("commit-graph-chain");
55 let chain_file = std::fs::File::open(&chain_file_path).map_err(|e| Error::Io {
56 err: e,
57 path: chain_file_path.clone(),
58 })?;
59 let mut files = Vec::new();
60 for line in BufReader::new(chain_file).lines() {
61 let hash = line.map_err(|e| Error::Io {
62 err: e,
63 path: chain_file_path.clone(),
64 })?;
65 let graph_file_path = commit_graphs_dir.join(format!("graph-{hash}.graph"));
66 files.push(File::at(&graph_file_path).map_err(|e| Error::File {
67 err: e,
68 path: graph_file_path.clone(),
69 })?);
70 }
71 Self::new(files)
72 }
73
74 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
77 let path = path.as_ref();
78 let file = File::at(path).map_err(|e| Error::File {
79 err: e,
80 path: path.to_owned(),
81 })?;
82 Self::new(vec![file])
83 }
84
85 pub fn from_info_dir(info_dir: impl AsRef<Path>) -> Result<Self, Error> {
87 Self::from_file(info_dir.as_ref().join("commit-graph"))
88 .or_else(|_| Self::from_commit_graphs_dir(info_dir.as_ref().join("commit-graphs")))
89 }
90
91 pub fn new(files: Vec<File>) -> Result<Self, Error> {
93 let num_commits: u64 = files.iter().map(|f| u64::from(f.num_commits())).sum();
94 if num_commits > u64::from(MAX_COMMITS) {
95 return Err(Error::TooManyCommits(num_commits));
96 }
97
98 for window in files.windows(2) {
99 let f1 = &window[0];
100 let f2 = &window[1];
101 if f1.object_hash() != f2.object_hash() {
102 return Err(Error::HashVersionMismatch {
103 path1: f1.path().to_owned(),
104 hash1: f1.object_hash(),
105 path2: f2.path().to_owned(),
106 hash2: f2.object_hash(),
107 });
108 }
109 }
110
111 Ok(Self { files })
112 }
113}
114
115impl TryFrom<&Path> for Graph {
116 type Error = Error;
117
118 fn try_from(path: &Path) -> Result<Self, Self::Error> {
119 if path.is_file() {
120 Self::from_file(path)
123 } else if path.is_dir() {
124 if path.join("commit-graph-chain").is_file() {
125 Self::from_commit_graphs_dir(path)
126 } else {
127 Self::from_info_dir(path)
128 }
129 } else {
130 Err(Error::InvalidPath(path.to_owned()))
131 }
132 }
133}