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 @@
1+use crate::file::{File, LexPosition};
2+use crate::graph::GraphPosition;
3+use byteorder::{BigEndian, ByteOrder};
4+use git_object::{borrowed, owned, SHA1_SIZE};
5+use quick_error::quick_error;
6+use std::convert::{TryFrom, TryInto};
7+use std::fmt::{Debug, Formatter};
8+use std::slice::Chunks;
9+
10+quick_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.
39+const NO_PARENT: u32 = 0x7000_0000;
40+const EXTENDED_EDGES_MASK: u32 = 0x8000_0000;
41+
42+pub 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+
53+impl<'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+
105+impl<'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+
120+impl<'a> Eq for Commit<'a> {}
121+
122+impl<'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+
128+pub struct ParentIterator<'a> {
129+ commit_data: &'a Commit<'a>,
130+ state: ParentIteratorState<'a>,
131+}
132+
133+impl<'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)]
212+enum ParentIteratorState<'a> {
213+ First,
214+ Second,
215+ Extra(Chunks<'a, u8>),
216+ Exhausted,
217+}
218+
219+#[derive(Clone, Copy, Debug)]
220+enum ParentEdge {
221+ None,
222+ GraphPosition(GraphPosition),
223+ ExtraEdgeIndex(u32),
224+}
225+
226+impl 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+
239+const LAST_EXTENDED_EDGE_MASK: u32 = 0x8000_0000;
240+
241+enum ExtraEdge {
242+ Internal(GraphPosition),
243+ Last(GraphPosition),
244+}
245+
246+impl 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+}