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 | } | ||