History

[commitgraph] Rename GraphFile -> File.

Conor Davis committed 6 years ago

Commit f4518221 parent d8c20078 files · +42 −42Browse files

Showing the changes to git-commitgraph/src/file/commit.rs (1 of 8 files). Show all

Unified · Split

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