1use crate::{
3 File, Position,
4 file::{self, EXTENDED_EDGES_MASK, LAST_EXTENDED_EDGE_MASK, NO_PARENT},
5};
6use gix_error::ExnMessageResult;
7use gix_error::message;
8use std::{
9 fmt::{Debug, Formatter},
10 slice::Chunks,
11};
12
13#[derive(Copy, Clone)]
15pub struct Commit<'a> {
16 file: &'a File,
17 pos: file::Position,
18 commit_timestamp: u64,
20 generation: u32,
21 parent1: ParentEdge,
22 parent2: ParentEdge,
23 root_tree_id: &'a gix_hash::oid,
24}
25
26#[inline]
27fn read_u32(b: &[u8]) -> u32 {
28 u32::from_be_bytes(b.try_into().unwrap())
29}
30
31impl<'a> Commit<'a> {
32 pub(crate) fn new(file: &'a File, pos: file::Position) -> Self {
33 let bytes = file.commit_data_bytes(pos);
34 Commit {
35 file,
36 pos,
37 root_tree_id: gix_hash::oid::from_bytes_unchecked(&bytes[..file.hash_len]),
38 parent1: ParentEdge::from_raw(read_u32(&bytes[file.hash_len..][..4])),
39 parent2: ParentEdge::from_raw(read_u32(&bytes[file.hash_len + 4..][..4])),
40 generation: read_u32(&bytes[file.hash_len + 8..][..4]) >> 2,
44 commit_timestamp: u64::from_be_bytes(bytes[file.hash_len + 8..][..8].try_into().unwrap())
45 & 0x0003_ffff_ffff,
46 }
47 }
48
49 pub fn committer_timestamp(&self) -> u64 {
53 self.commit_timestamp
54 }
55
56 pub fn generation(&self) -> u32 {
61 self.generation
62 }
63
64 pub fn iter_parents(self) -> Parents<'a> {
66 Parents {
70 commit_data: self,
71 state: ParentIteratorState::First,
72 }
73 }
74
75 pub fn id(&self) -> &'a gix_hash::oid {
77 self.file.id_at(self.pos)
78 }
79
80 pub fn parent1(&self) -> ExnMessageResult<Option<Position>> {
82 self.iter_parents().next().transpose()
83 }
84
85 pub fn position(&self) -> file::Position {
87 self.pos
88 }
89
90 pub fn root_tree_id(&self) -> &gix_hash::oid {
92 self.root_tree_id
93 }
94}
95
96impl Debug for Commit<'_> {
97 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
98 write!(
99 f,
100 "Commit {{ id: {}, lex_pos: {}, generation: {}, root_tree_id: {}, parent1: {:?}, parent2: {:?} }}",
101 self.id(),
102 self.pos,
103 self.generation(),
104 self.root_tree_id(),
105 self.parent1,
106 self.parent2,
107 )
108 }
109}
110
111impl Eq for Commit<'_> {}
112
113impl PartialEq for Commit<'_> {
114 fn eq(&self, other: &Self) -> bool {
115 std::ptr::eq(self.file, other.file) && self.pos == other.pos
116 }
117}
118
119pub struct Parents<'a> {
121 commit_data: Commit<'a>,
122 state: ParentIteratorState<'a>,
123}
124
125impl Iterator for Parents<'_> {
126 type Item = ExnMessageResult<Position>;
127
128 fn next(&mut self) -> Option<Self::Item> {
129 let state = std::mem::replace(&mut self.state, ParentIteratorState::Exhausted);
130 match state {
131 ParentIteratorState::First => match self.commit_data.parent1 {
132 ParentEdge::None => match self.commit_data.parent2 {
133 ParentEdge::None => None,
134 _ => Some(Err(message!(
135 "commit {} has a second parent but not a first parent",
136 self.commit_data.id()
137 )
138 .into())),
139 },
140 ParentEdge::GraphPosition(pos) => {
141 self.state = ParentIteratorState::Second;
142 Some(Ok(pos))
143 }
144 ParentEdge::ExtraEdgeIndex(_) => Some(Err(message!(
145 "commit {}'s first parent is an extra edge index, which is invalid",
146 self.commit_data.id(),
147 )
148 .into())),
149 },
150 ParentIteratorState::Second => match self.commit_data.parent2 {
151 ParentEdge::None => None,
152 ParentEdge::GraphPosition(pos) => Some(Ok(pos)),
153 ParentEdge::ExtraEdgeIndex(extra_edge_index) => {
154 if let Some(extra_edges_list) = self.commit_data.file.extra_edges_data() {
155 let start_offset: usize = extra_edge_index
156 .try_into()
157 .expect("an architecture able to hold 32 bits of integer");
158 let start_offset = start_offset
159 .checked_mul(4)
160 .expect("an extended edge index small enough to fit in usize");
161 if let Some(tail) = extra_edges_list.get(start_offset..) {
162 self.state = ParentIteratorState::Extra(tail.chunks(4));
163 self.next()
166 } else {
167 Some(Err(message!(
168 "commit {}'s extra edges overflows the commit-graph file's extra edges list",
169 self.commit_data.id()
170 )
171 .into()))
172 }
173 } else {
174 Some(Err(message!(
175 "commit {} has extra edges, but commit-graph file has no extra edges list",
176 self.commit_data.id()
177 )
178 .into()))
179 }
180 }
181 },
182 ParentIteratorState::Extra(mut chunks) => {
183 if let Some(chunk) = chunks.next() {
184 let extra_edge = read_u32(chunk);
185 match ExtraEdge::from_raw(extra_edge) {
186 ExtraEdge::Internal(pos) => {
187 self.state = ParentIteratorState::Extra(chunks);
188 Some(Ok(pos))
189 }
190 ExtraEdge::Last(pos) => Some(Ok(pos)),
191 }
192 } else {
193 Some(Err(message!(
194 "commit {}'s extra edges overflows the commit-graph file's extra edges list",
195 self.commit_data.id()
196 )
197 .into()))
198 }
199 }
200 ParentIteratorState::Exhausted => None,
201 }
202 }
203
204 fn size_hint(&self) -> (usize, Option<usize>) {
205 match (&self.state, self.commit_data.parent1, self.commit_data.parent2) {
206 (ParentIteratorState::First, ParentEdge::None, ParentEdge::None) => (0, Some(0)),
207 (ParentIteratorState::First, ParentEdge::None, _) => (1, Some(1)),
208 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::None) => (1, Some(1)),
209 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::GraphPosition(_)) => (2, Some(2)),
210 (ParentIteratorState::First, ParentEdge::GraphPosition(_), ParentEdge::ExtraEdgeIndex(_)) => (3, None),
211 (ParentIteratorState::First, ParentEdge::ExtraEdgeIndex(_), _) => (1, Some(1)),
212 (ParentIteratorState::Second, _, ParentEdge::None) => (0, Some(0)),
213 (ParentIteratorState::Second, _, ParentEdge::GraphPosition(_)) => (1, Some(1)),
214 (ParentIteratorState::Second, _, ParentEdge::ExtraEdgeIndex(_)) => (2, None),
215 (ParentIteratorState::Extra(_), _, _) => (1, None),
216 (ParentIteratorState::Exhausted, _, _) => (0, Some(0)),
217 }
218 }
219}
220
221#[derive(Debug)]
222enum ParentIteratorState<'a> {
223 First,
224 Second,
225 Extra(Chunks<'a, u8>),
226 Exhausted,
227}
228
229#[derive(Clone, Copy, Debug)]
230enum ParentEdge {
231 None,
232 GraphPosition(Position),
233 ExtraEdgeIndex(u32),
234}
235
236impl ParentEdge {
237 pub fn from_raw(raw: u32) -> ParentEdge {
238 if raw == NO_PARENT {
239 return ParentEdge::None;
240 }
241 if raw & EXTENDED_EDGES_MASK != 0 {
242 ParentEdge::ExtraEdgeIndex(raw & !EXTENDED_EDGES_MASK)
243 } else {
244 ParentEdge::GraphPosition(Position(raw))
245 }
246 }
247}
248
249enum ExtraEdge {
250 Internal(Position),
251 Last(Position),
252}
253
254impl ExtraEdge {
255 pub fn from_raw(raw: u32) -> Self {
256 if raw & LAST_EXTENDED_EDGE_MASK != 0 {
257 Self::Last(Position(raw & !LAST_EXTENDED_EDGE_MASK))
258 } else {
259 Self::Internal(Position(raw))
260 }
261 }
262}