History

[commitgraph] implement basic, low-level read API

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.

Conor Davis committed 6 years ago

Commit d1f0e9c1 parent 1b42b3153 files · +1429 −-23Browse files

Showing the changes to git-commitgraph/src/graph (3 of 53 files). Show all

Unified · Split

3 files
  1. git-commitgraph/src/graph/access.rs +83 −0
  2. git-commitgraph/src/graph/init.rs +104 −0
  3. git-commitgraph/src/graph/mod.rs +23 −0
git-commitgraph/src/graph/access.rs+83−0
@@ -0,0 +1,83 @@
1use crate::graph::GraphPosition;
2use crate::graph_file::LexPosition;
3use crate::{CommitData, Graph, GraphFile};
4use git_object::borrowed;
5
6impl 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
41impl 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
74struct LookupByIdResult<'a> {
75 pub file: &'a GraphFile,
76 pub graph_pos: GraphPosition,
77 pub lex_pos: LexPosition,
78}
79
80struct LookupByPositionResult<'a> {
81 pub file: &'a GraphFile,
82 pub lex_pos: LexPosition,
83}
git-commitgraph/src/graph/init.rs+104−0
@@ -0,0 +1,104 @@
1use crate::{graph_file, Graph, GraphFile, MAX_COMMITS};
2use git_object::HashKind;
3use quick_error::quick_error;
4use std::io::{BufRead, BufReader};
5use std::path::{Path, PathBuf};
6
7quick_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
50impl 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}
git-commitgraph/src/graph/mod.rs+23−0
@@ -0,0 +1,23 @@
1mod access;
2mod init;
3
4use crate::GraphFile;
5use 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.
12pub struct Graph {
13 files: Vec<GraphFile>,
14}
15
16#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
17pub struct GraphPosition(pub u32);
18
19impl Display for GraphPosition {
20 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
21 self.0.fmt(f)
22 }
23}