This is just the first pass at ensuring we can read commit-graph files correctly. It does not support anything interesting like reachability, merge-base, or ahead-behind queries.
Showing the changes to git-commitgraph/src/graph (3 of 53 files). Show all
Unified · Split
| @@ -0,0 +1,83 @@ | ||
| 1 | +use crate::graph::GraphPosition; | |
| 2 | +use crate::graph_file::LexPosition; | |
| 3 | +use crate::{CommitData, Graph, GraphFile}; | |
| 4 | +use git_object::borrowed; | |
| 5 | + | |
| 6 | +impl Graph { | |
| 7 | + pub fn commit_at(&self, pos: GraphPosition) -> CommitData { | |
| 8 | + let r = self.lookup_by_pos(pos); | |
| 9 | + r.file.commit_at(r.lex_pos) | |
| 10 | + } | |
| 11 | + | |
| 12 | + pub fn commit_by_id(&self, id: borrowed::Id) -> Option<CommitData> { | |
| 13 | + let r = self.lookup_by_id(id)?; | |
| 14 | + Some(r.file.commit_at(r.lex_pos)) | |
| 15 | + } | |
| 16 | + | |
| 17 | + pub fn id_at(&self, pos: GraphPosition) -> borrowed::Id { | |
| 18 | + let r = self.lookup_by_pos(pos); | |
| 19 | + r.file.id_at(r.lex_pos) | |
| 20 | + } | |
| 21 | + | |
| 22 | + /// Iterate over commits in unsorted order. | |
| 23 | + pub fn iter_commits(&self) -> impl Iterator<Item = CommitData> { | |
| 24 | + self.files.iter().flat_map(|file| file.iter_commits()) | |
| 25 | + } | |
| 26 | + | |
| 27 | + /// Iterate over commit IDs in unsorted order. | |
| 28 | + pub fn iter_ids(&self) -> impl Iterator<Item = borrowed::Id> { | |
| 29 | + self.files.iter().flat_map(|file| file.iter_ids()) | |
| 30 | + } | |
| 31 | + | |
| 32 | + pub fn lookup(&self, id: borrowed::Id) -> Option<GraphPosition> { | |
| 33 | + Some(self.lookup_by_id(id)?.graph_pos) | |
| 34 | + } | |
| 35 | + | |
| 36 | + pub fn num_commits(&self) -> u32 { | |
| 37 | + self.files.iter().map(|f| f.num_commits()).sum() | |
| 38 | + } | |
| 39 | +} | |
| 40 | + | |
| 41 | +impl Graph { | |
| 42 | + fn lookup_by_id(&self, id: borrowed::Id) -> Option<LookupByIdResult> { | |
| 43 | + let mut current_file_start = 0; | |
| 44 | + for file in self.files.iter() { | |
| 45 | + if let Some(lex_pos) = file.lookup(id) { | |
| 46 | + return Some(LookupByIdResult { | |
| 47 | + file, | |
| 48 | + lex_pos, | |
| 49 | + graph_pos: GraphPosition(current_file_start + lex_pos.0), | |
| 50 | + }); | |
| 51 | + } | |
| 52 | + current_file_start += file.num_commits(); | |
| 53 | + } | |
| 54 | + None | |
| 55 | + } | |
| 56 | + | |
| 57 | + fn lookup_by_pos(&self, pos: GraphPosition) -> LookupByPositionResult { | |
| 58 | + let mut remaining = pos.0; | |
| 59 | + for file in self.files.iter() { | |
| 60 | + match remaining.checked_sub(file.num_commits()) { | |
| 61 | + Some(v) => remaining = v, | |
| 62 | + None => { | |
| 63 | + return LookupByPositionResult { | |
| 64 | + file, | |
| 65 | + lex_pos: LexPosition(remaining), | |
| 66 | + } | |
| 67 | + } | |
| 68 | + } | |
| 69 | + } | |
| 70 | + panic!("graph position too large: {}", pos.0); | |
| 71 | + } | |
| 72 | +} | |
| 73 | + | |
| 74 | +struct LookupByIdResult<'a> { | |
| 75 | + pub file: &'a GraphFile, | |
| 76 | + pub graph_pos: GraphPosition, | |
| 77 | + pub lex_pos: LexPosition, | |
| 78 | +} | |
| 79 | + | |
| 80 | +struct LookupByPositionResult<'a> { | |
| 81 | + pub file: &'a GraphFile, | |
| 82 | + pub lex_pos: LexPosition, | |
| 83 | +} | |
| @@ -0,0 +1,104 @@ | ||
| 1 | +use crate::{graph_file, Graph, GraphFile, MAX_COMMITS}; | |
| 2 | +use git_object::HashKind; | |
| 3 | +use quick_error::quick_error; | |
| 4 | +use std::io::{BufRead, BufReader}; | |
| 5 | +use std::path::{Path, PathBuf}; | |
| 6 | + | |
| 7 | +quick_error! { | |
| 8 | + #[derive(Debug)] | |
| 9 | + pub enum Error { | |
| 10 | + Corrupt(msg: String, path: PathBuf) { | |
| 11 | + display("{}", msg) | |
| 12 | + } | |
| 13 | + GraphFile(err: graph_file::Error, path: PathBuf) { | |
| 14 | + display("{}", path.display()) | |
| 15 | + source(err) | |
| 16 | + } | |
| 17 | + HashVersionMismatch(path1: PathBuf, hash1: HashKind, path2: PathBuf, hash2: HashKind) { | |
| 18 | + display( | |
| 19 | + "Commit-graph files mismatch: '{}' uses hash {:?}, but '{}' uses hash {:?}", | |
| 20 | + path1.display(), | |
| 21 | + hash1, | |
| 22 | + path2.display(), | |
| 23 | + hash2, | |
| 24 | + ) | |
| 25 | + } | |
| 26 | + Io(err: std::io::Error, path: PathBuf) { | |
| 27 | + display("Could not open commit-graph graph_file at '{}'", path.display()) | |
| 28 | + source(err) | |
| 29 | + } | |
| 30 | + TooManyCommits(num_commits: u64) { | |
| 31 | + display( | |
| 32 | + "Commit-graph files contain {} commits altogether, but only {} commits are allowed", | |
| 33 | + num_commits, | |
| 34 | + MAX_COMMITS, | |
| 35 | + ) | |
| 36 | + } | |
| 37 | + // This might actually be legal... | |
| 38 | + VersionMismatch(path1: PathBuf, version1: graph_file::Kind, path2: PathBuf, version2: graph_file::Kind) { | |
| 39 | + display( | |
| 40 | + "Commit-graph files mismatch: '{}' is version {:?}, but '{}' is version {:?}", | |
| 41 | + path1.display(), | |
| 42 | + version1, | |
| 43 | + path2.display(), | |
| 44 | + version2, | |
| 45 | + ) | |
| 46 | + } | |
| 47 | + } | |
| 48 | +} | |
| 49 | + | |
| 50 | +impl Graph { | |
| 51 | + pub fn from_object_dir(object_dir: impl AsRef<Path>) -> Result<Self, Error> { | |
| 52 | + Self::from_single_file(object_dir.as_ref()).or_else(|_| Self::from_split_chain(object_dir.as_ref())) | |
| 53 | + } | |
| 54 | + | |
| 55 | + pub fn from_single_file(object_dir: impl AsRef<Path>) -> Result<Self, Error> { | |
| 56 | + let single_graph_file = object_dir.as_ref().join("info").join("commit-graph"); | |
| 57 | + let file = GraphFile::at(&single_graph_file).map_err(|e| Error::GraphFile(e, single_graph_file.clone()))?; | |
| 58 | + Self::new(vec![file]) | |
| 59 | + } | |
| 60 | + | |
| 61 | + pub fn from_split_chain(object_dir: impl AsRef<Path>) -> Result<Self, Error> { | |
| 62 | + let commit_graphs_dir = object_dir.as_ref().join("info").join("commit-graphs"); | |
| 63 | + let chain_file_path = commit_graphs_dir.join("commit-graph-chain"); | |
| 64 | + let chain_file = std::fs::File::open(&chain_file_path).map_err(|e| Error::Io(e, chain_file_path.clone()))?; | |
| 65 | + let mut files: Vec<GraphFile> = Vec::new(); | |
| 66 | + for line in BufReader::new(chain_file).lines() { | |
| 67 | + let line = line.map_err(|e| Error::Io(e, chain_file_path.clone()))?; | |
| 68 | + let graph_filename = format!("graph-{}.graph", line); | |
| 69 | + let graph_file_path = commit_graphs_dir.join(graph_filename); | |
| 70 | + files.push(GraphFile::at(&graph_file_path).map_err(|e| Error::GraphFile(e, graph_file_path.clone()))?); | |
| 71 | + } | |
| 72 | + Self::new(files) | |
| 73 | + } | |
| 74 | + | |
| 75 | + pub fn new(files: Vec<GraphFile>) -> Result<Self, Error> { | |
| 76 | + let num_commits: u64 = files.iter().map(|f| f.num_commits() as u64).sum(); | |
| 77 | + if num_commits > MAX_COMMITS as u64 { | |
| 78 | + return Err(Error::TooManyCommits(num_commits)); | |
| 79 | + } | |
| 80 | + | |
| 81 | + for window in files.windows(2) { | |
| 82 | + let f1 = &window[0]; | |
| 83 | + let f2 = &window[1]; | |
| 84 | + if f1.kind() != f2.kind() { | |
| 85 | + return Err(Error::VersionMismatch( | |
| 86 | + f1.path().to_owned(), | |
| 87 | + f1.kind(), | |
| 88 | + f2.path().to_owned(), | |
| 89 | + f2.kind(), | |
| 90 | + )); | |
| 91 | + } | |
| 92 | + if f1.hash_kind() != f2.hash_kind() { | |
| 93 | + return Err(Error::HashVersionMismatch( | |
| 94 | + f1.path().to_owned(), | |
| 95 | + f1.hash_kind(), | |
| 96 | + f2.path().to_owned(), | |
| 97 | + f2.hash_kind(), | |
| 98 | + )); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | + Ok(Self { files }) | |
| 103 | + } | |
| 104 | +} | |
| @@ -0,0 +1,23 @@ | ||
| 1 | +mod access; | |
| 2 | +mod init; | |
| 3 | + | |
| 4 | +use crate::GraphFile; | |
| 5 | +use std::fmt::{Display, Formatter}; | |
| 6 | + | |
| 7 | +/// A complete commit graph. | |
| 8 | +/// | |
| 9 | +/// The data in the commit graph may come from a monolithic `objects/info/commit-graph` file, or it | |
| 10 | +/// may come from one or more `objects/info/commit-graphs/graph-*.graph` files. These files are | |
| 11 | +/// generated via `git commit-graph write ...` commands. | |
| 12 | +pub struct Graph { | |
| 13 | + files: Vec<GraphFile>, | |
| 14 | +} | |
| 15 | + | |
| 16 | +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] | |
| 17 | +pub struct GraphPosition(pub u32); | |
| 18 | + | |
| 19 | +impl Display for GraphPosition { | |
| 20 | + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
| 21 | + self.0.fmt(f) | |
| 22 | + } | |
| 23 | +} | |