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 @@
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+}
git-commitgraph/src/graph/init.rs+104−0
@@ -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+}
git-commitgraph/src/graph/mod.rs+23−0
@@ -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+}