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_file/commit_data.rs (1 of 53 files). Show all

Unified · Split

git-commitgraph/src/graph_file/commit_data.rs+255−0
@@ -0,0 +1,255 @@
1use crate::graph::GraphPosition;
2use crate::graph_file::LexPosition;
3use crate::GraphFile;
4use byteorder::{BigEndian, ByteOrder};
5use git_object::{borrowed, owned, SHA1_SIZE};
6use quick_error::quick_error;
7use std::convert::{TryFrom, TryInto};
8use std::fmt::{Debug, Formatter};
9use std::slice::Chunks;
10
11quick_error! {
12 #[derive(Debug)]
13 pub enum Error {
14 ExtraEdgesListOverflow(commit: owned::Id) {
15 display(
16 "commit {}'s extra edges overflows the commit-graph file's extra edges list",
17 commit,
18 )
19 }
20 FirstParentIsExtraEdgeIndex(commit: owned::Id) {
21 display(
22 "commit {}'s first parent is an extra edge index, which is invalid",
23 commit,
24 )
25 }
26 MissingExtraEdgesList(commit: owned::Id) {
27 display(
28 "commit {} has extra edges, but commit-graph file has no extra edges list",
29 commit,
30 )
31 }
32 SecondParentWithoutFirstParent(commit: owned::Id) {
33 display("commit {} has a second parent but not a first parent", commit)
34 }
35 }
36}
37
38// Note that git's commit-graph-format.txt as of v2.28.0 gives an incorrect value 0x0700_0000 for
39// NO_PARENT.
40const NO_PARENT: u32 = 0x7000_0000;
41const EXTENDED_EDGES_MASK: u32 = 0x8000_0000;
42
43pub struct CommitData<'a> {
44 file: &'a GraphFile,
45 lex_pos: LexPosition,
46 // We can parse the below fields lazily if needed.
47 commit_timestamp: u64,
48 generation: u32,
49 parent1: ParentEdge,
50 parent2: ParentEdge,
51 root_tree_id: borrowed::Id<'a>,
52}
53
54impl<'a> CommitData<'a> {
55 pub(crate) fn new(file: &'a GraphFile, pos: LexPosition) -> Self {
56 let bytes = file.commit_data_bytes(pos);
57 CommitData {
58 file,
59 lex_pos: pos,
60 root_tree_id: borrowed::Id::try_from(&bytes[..SHA1_SIZE]).expect("20 bytes SHA1 to be alright"),
61 parent1: ParentEdge::from_raw(BigEndian::read_u32(&bytes[SHA1_SIZE..SHA1_SIZE + 4])),
62 parent2: ParentEdge::from_raw(BigEndian::read_u32(&bytes[SHA1_SIZE + 4..SHA1_SIZE + 8])),
63 generation: BigEndian::read_u32(&bytes[SHA1_SIZE + 8..SHA1_SIZE + 12]) >> 2,
64 commit_timestamp: BigEndian::read_u64(&bytes[SHA1_SIZE + 8..SHA1_SIZE + 16]) & 0x0003_ffff_ffff,
65 }
66 }
67
68 /// Returns the committer timestamp of this commit.
69 ///
70 /// The value is the number of seconds since 1970-01-01 00:00:00 UTC.
71 pub fn committer_timestamp(&self) -> u64 {
72 self.commit_timestamp
73 }
74
75 /// Returns the generation number of this commit.
76 ///
77 /// Commits without parents have generation number 1. Commits with parents have a generation
78 /// number that is the max of their parents' generation numbers + 1.
79 pub fn generation(&self) -> u32 {
80 self.generation
81 }
82
83 pub fn iter_parent_indices(&'a self) -> impl Iterator<Item = Result<GraphPosition, Error>> + 'a {
84 // I didn't find a combinator approach that a) was as strict as ParentIterator, b) supported
85 // fuse-after-first-error behavior, and b) was significantly shorter or more understandable
86 // than ParentIterator. So here we are.
87 ParentIterator {
88 commit_data: self,
89 state: ParentIteratorState::First,
90 }
91 }
92
93 pub fn id(&self) -> borrowed::Id {
94 self.file.id_at(self.lex_pos)
95 }
96
97 pub fn parent1(&self) -> Result<Option<GraphPosition>, Error> {
98 self.iter_parent_indices().next().transpose()
99 }
100
101 pub fn root_tree_id(&self) -> borrowed::Id {
102 self.root_tree_id
103 }
104}
105
106impl<'a> Debug for CommitData<'a> {
107 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108 write!(
109 f,
110 "CommitData {{ id: {}, lex_pos: {}, generation: {}, root_tree_oid: {}, parent1: {:?}, parent2: {:?} }}",
111 self.id(),
112 self.lex_pos,
113 self.generation(),
114 self.root_tree_id(),
115 self.parent1,
116 self.parent2,
117 )
118 }
119}
120
121impl<'a> Eq for CommitData<'a> {}
122
123impl<'a> PartialEq for CommitData<'a> {
124 fn eq(&self, other: &Self) -> bool {
125 self.file as *const GraphFile == other.file as *const GraphFile && self.lex_pos == other.lex_pos
126 }
127}
128
129pub struct ParentIterator<'a> {
130 commit_data: &'a CommitData<'a>,
131 state: ParentIteratorState<'a>,
132}
133
134impl<'a> Iterator for ParentIterator<'a> {
135 type Item = Result<GraphPosition, Error>;
136
137 fn next(&mut self) -> Option<Self::Item> {
138 let state = std::mem::replace(&mut self.state, ParentIteratorState::Exhausted);
139 match state {
140 ParentIteratorState::First => match self.commit_data.parent1 {
141 ParentEdge::None => match self.commit_data.parent2 {
142 ParentEdge::None => None,
143 _ => Some(Err(Error::SecondParentWithoutFirstParent(self.commit_data.id().into()))),
144 },
145 ParentEdge::GraphPosition(pos) => {
146 self.state = ParentIteratorState::Second;
147 Some(Ok(pos))
148 }
149 ParentEdge::ExtraEdgeIndex(_) => {
150 Some(Err(Error::FirstParentIsExtraEdgeIndex(self.commit_data.id().into())))
151 }
152 },
153 ParentIteratorState::Second => match self.commit_data.parent2 {
154 ParentEdge::None => None,
155 ParentEdge::GraphPosition(pos) => Some(Ok(pos)),
156 ParentEdge::ExtraEdgeIndex(extra_edge_index) => {
157 if let Some(extra_edges_list) = self.commit_data.file.extra_edges_data() {
158 let start_offset: usize = extra_edge_index
159 .try_into()
160 .expect("an architecture able to hold 32 bits of integer");
161 let start_offset = start_offset
162 .checked_mul(4)
163 .expect("an extended edge index small enough to fit in usize");
164 if let Some(tail) = extra_edges_list.get(start_offset..) {
165 self.state = ParentIteratorState::Extra(tail.chunks(4));
166 // This recursive call is what blocks me from replacing ParentIterator
167 // with a std::iter::from_fn closure.
168 self.next()
169 } else {
170 Some(Err(Error::ExtraEdgesListOverflow(self.commit_data.id().into())))
171 }
172 } else {
173 Some(Err(Error::MissingExtraEdgesList(self.commit_data.id().into())))
174 }
175 }
176 },
177 ParentIteratorState::Extra(mut chunks) => {
178 if let Some(chunk) = chunks.next() {
179 let extra_edge = BigEndian::read_u32(chunk);
180 match ExtraEdge::from_raw(extra_edge) {
181 ExtraEdge::Internal(pos) => {
182 self.state = ParentIteratorState::Extra(chunks);
183 Some(Ok(pos))
184 }
185 ExtraEdge::Last(pos) => Some(Ok(pos)),
186 }
187 } else {
188 Some(Err(Error::ExtraEdgesListOverflow(self.commit_data.id().into())))
189 }
190 }
191 ParentIteratorState::Exhausted => None,
192 }
193 }
194
195 fn size_hint(&self) -> (usize, Option<usize>) {
196 match (&self.state, self.commit_data.parent1, self.commit_data.parent2) {
197 (ParentIteratorState::First, ParentEdge::None, ParentEdge::None) => (0, Some(0)),
198 (ParentIteratorState::First, ParentEdge::None, _) => (1, Some(1)),
199 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::None) => (1, Some(1)),
200 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::GraphPosition(_)) => (2, Some(2)),
201 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::ExtraEdgeIndex(_)) => (3, None),
202 (ParentIteratorState::First, ParentEdge::ExtraEdgeIndex(_), _) => (1, Some(1)),
203 (ParentIteratorState::Second, _, ParentEdge::None) => (0, Some(0)),
204 (ParentIteratorState::Second, _, ParentEdge::GraphPosition(_)) => (1, Some(1)),
205 (ParentIteratorState::Second, _, ParentEdge::ExtraEdgeIndex(_)) => (2, None),
206 (ParentIteratorState::Extra(_), _, _) => (1, None),
207 (ParentIteratorState::Exhausted, _, _) => (0, Some(0)),
208 }
209 }
210}
211
212#[derive(Debug)]
213enum ParentIteratorState<'a> {
214 First,
215 Second,
216 Extra(Chunks<'a, u8>),
217 Exhausted,
218}
219
220#[derive(Clone, Copy, Debug)]
221enum ParentEdge {
222 None,
223 GraphPosition(GraphPosition),
224 ExtraEdgeIndex(u32),
225}
226
227impl ParentEdge {
228 pub fn from_raw(raw: u32) -> ParentEdge {
229 if raw == NO_PARENT {
230 return ParentEdge::None;
231 }
232 if raw & EXTENDED_EDGES_MASK != 0 {
233 ParentEdge::ExtraEdgeIndex(raw & !EXTENDED_EDGES_MASK)
234 } else {
235 ParentEdge::GraphPosition(GraphPosition(raw))
236 }
237 }
238}
239
240const LAST_EXTENDED_EDGE_MASK: u32 = 0x8000_0000;
241
242enum ExtraEdge {
243 Internal(GraphPosition),
244 Last(GraphPosition),
245}
246
247impl ExtraEdge {
248 pub fn from_raw(raw: u32) -> Self {
249 if raw & LAST_EXTENDED_EDGE_MASK != 0 {
250 Self::Last(GraphPosition(raw & !LAST_EXTENDED_EDGE_MASK))
251 } else {
252 Self::Internal(GraphPosition(raw))
253 }
254 }
255}