1use gix_error::ExnMessageResult;
2use std::{borrow::Cow, ops::Range};
3
4use bstr::BStr;
5use gix_hash::{ObjectId, oid};
6
7use crate::{
8 CommitRefIter,
9 bstr::ByteSlice,
10 commit::{decode, signature_field_name},
11 parse,
12 signature::SignedData,
13};
14
15#[derive(Copy, Clone)]
16pub(crate) enum SignatureKind {
17 Author,
18 Committer,
19}
20
21#[derive(Default, Copy, Clone)]
22pub(crate) enum State {
23 #[default]
24 Tree,
25 Parents,
26 Signature {
27 of: SignatureKind,
28 },
29 Encoding,
30 ExtraHeaders,
31 Message,
32}
33
34impl<'a> CommitRefIter<'a> {
36 pub fn from_bytes(data: &'a [u8], hash_kind: gix_hash::Kind) -> CommitRefIter<'a> {
39 CommitRefIter {
40 data,
41 state: State::default(),
42 hash_kind,
43 }
44 }
45}
46
47impl<'a> CommitRefIter<'a> {
49 pub fn signature(
55 data: &'a [u8],
56 hash_kind: gix_hash::Kind,
57 ) -> ExnMessageResult<Option<(Cow<'a, BStr>, SignedData<'a>)>> {
58 let mut signature_and_range = None;
59
60 let raw_tokens = CommitRefIterRaw {
61 data,
62 state: State::default(),
63 offset: 0,
64 hash_kind,
65 };
66 for token in raw_tokens {
67 let token = token?;
68 if let Token::ExtraHeader((name, value)) = &token.token
69 && *name == signature_field_name(hash_kind)
70 {
71 signature_and_range = Some((value.clone(), token.token_range));
74 break;
75 }
76 }
77
78 Ok(signature_and_range.map(|(sig, signature_range)| (sig, SignedData::new(data, signature_range))))
79 }
80
81 pub fn tree_id(&mut self) -> ExnMessageResult<ObjectId> {
89 let tree_id = self.next().ok_or_else(missing_field)??;
90 Ok(Token::try_into_id(tree_id).ok_or_else(missing_field)?)
91 }
92
93 pub fn parent_ids(self) -> impl Iterator<Item = gix_hash::ObjectId> + 'a {
97 self.filter_map(|t| match t {
98 Ok(Token::Parent { id }) => Some(id),
99 _ => None,
100 })
101 }
102
103 pub fn signatures(self) -> impl Iterator<Item = gix_actor::SignatureRef<'a>> + 'a {
109 self.filter_map(|t| match t {
110 Ok(Token::Author { signature } | Token::Committer { signature }) => Some(signature),
111 _ => None,
112 })
113 }
114
115 pub fn committer(mut self) -> ExnMessageResult<gix_actor::SignatureRef<'a>> {
117 self.find_map(|t| match t {
118 Ok(Token::Committer { signature }) => Some(Ok(signature)),
119 Err(err) => Some(Err(err)),
120 _ => None,
121 })
122 .ok_or_else(missing_field)?
123 }
124
125 pub fn author(mut self) -> ExnMessageResult<gix_actor::SignatureRef<'a>> {
129 self.find_map(|t| match t {
130 Ok(Token::Author { signature }) => Some(Ok(signature)),
131 Err(err) => Some(Err(err)),
132 _ => None,
133 })
134 .ok_or_else(missing_field)?
135 }
136
137 pub fn message(mut self) -> ExnMessageResult<&'a BStr> {
142 self.find_map(|t| match t {
143 Ok(Token::Message(msg)) => Some(Ok(msg)),
144 Err(err) => Some(Err(err)),
145 _ => None,
146 })
147 .transpose()
148 .map(Option::unwrap_or_default)
149 }
150}
151
152fn missing_field() -> gix_error::Message {
153 crate::decode::empty_error()
154}
155
156impl<'a> CommitRefIter<'a> {
157 #[inline]
158 fn next_inner(
159 mut i: &'a [u8],
160 state: &mut State,
161 hash_kind: gix_hash::Kind,
162 ) -> ExnMessageResult<(&'a [u8], Token<'a>)> {
163 let input = &mut i;
164 match Self::next_inner_(input, state, hash_kind) {
165 Ok(token) => Ok((*input, token)),
166 Err(err) => Err(err),
167 }
168 }
169
170 fn next_inner_(input: &mut &'a [u8], state: &mut State, hash_kind: gix_hash::Kind) -> ExnMessageResult<Token<'a>> {
171 use State::*;
172 Ok(match state {
173 Tree => {
174 let tree = parse::header_field(input, b"tree", |value| parse::hex_hash(value, hash_kind))?;
175 *state = State::Parents;
176 Token::Tree {
177 id: ObjectId::from_hex(tree).expect("parsing validation"),
178 }
179 }
180 Parents => {
181 if input.starts_with(b"parent ") {
182 let parent = parse::header_field(input, b"parent", |value| parse::hex_hash(value, hash_kind))?;
183 Token::Parent {
184 id: ObjectId::from_hex(parent).expect("parsing validation"),
185 }
186 } else {
187 *state = State::Signature {
188 of: SignatureKind::Author,
189 };
190 Self::next_inner_(input, state, hash_kind)?
191 }
192 }
193 Signature { of } => {
194 let who = *of;
195 let field_name = match of {
196 SignatureKind::Author => {
197 *of = SignatureKind::Committer;
198 &b"author"[..]
199 }
200 SignatureKind::Committer => {
201 *state = State::Encoding;
202 &b"committer"[..]
203 }
204 };
205 let signature = parse::header_field(input, field_name, parse::signature)?;
206 match who {
207 SignatureKind::Author => Token::Author { signature },
208 SignatureKind::Committer => Token::Committer { signature },
209 }
210 }
211 Encoding => {
212 *state = State::ExtraHeaders;
213 if input.starts_with(b"encoding ") {
214 let encoding = parse::header_field(input, b"encoding", Ok)?;
215 Token::Encoding(encoding.as_bstr())
216 } else {
217 Self::next_inner_(input, state, hash_kind)?
218 }
219 }
220 ExtraHeaders => {
221 if input.starts_with(b"\n") {
222 *state = State::Message;
223 Self::next_inner_(input, state, hash_kind)?
224 } else {
225 let before = *input;
226 {
227 let extra_header = parse::any_header_field_multi_line(input)
228 .map(|(k, o)| (k.as_bstr(), Cow::Owned(o)))
229 .or_else(|_| {
230 *input = before;
231 parse::any_header_field(input).map(|(k, o)| (k.as_bstr(), Cow::Borrowed(o.as_bstr())))
232 })?;
233 Token::ExtraHeader(extra_header)
234 }
235 }
236 }
237 Message => {
238 let message = decode::message(input)?;
239 debug_assert!(
240 input.is_empty(),
241 "we should have consumed all data - otherwise iter may go forever"
242 );
243 Token::Message(message)
244 }
245 })
246 }
247}
248
249impl<'a> Iterator for CommitRefIter<'a> {
250 type Item = ExnMessageResult<Token<'a>>;
251
252 fn next(&mut self) -> Option<Self::Item> {
253 if self.data.is_empty() {
254 return None;
255 }
256 match Self::next_inner(self.data, &mut self.state, self.hash_kind) {
257 Ok((data, token)) => {
258 self.data = data;
259 Some(Ok(token))
260 }
261 Err(err) => {
262 self.data = &[];
263 Some(Err(err))
264 }
265 }
266 }
267}
268
269struct CommitRefIterRaw<'a> {
271 data: &'a [u8],
272 state: State,
273 offset: usize,
274 hash_kind: gix_hash::Kind,
275}
276
277impl<'a> Iterator for CommitRefIterRaw<'a> {
278 type Item = ExnMessageResult<RawToken<'a>>;
279
280 fn next(&mut self) -> Option<Self::Item> {
281 if self.data.is_empty() {
282 return None;
283 }
284 match CommitRefIter::next_inner(self.data, &mut self.state, self.hash_kind) {
285 Ok((remaining, token)) => {
286 let consumed = self.data.len() - remaining.len();
287 let start = self.offset;
288 let end = start + consumed;
289 self.offset = end;
290
291 self.data = remaining;
292 Some(Ok(RawToken {
293 token,
294 token_range: start..end,
295 }))
296 }
297 Err(err) => {
298 self.data = &[];
299 Some(Err(err))
300 }
301 }
302 }
303}
304
305struct RawToken<'a> {
307 token: Token<'a>,
309 token_range: Range<usize>,
310}
311
312#[expect(missing_docs)]
314#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
315pub enum Token<'a> {
316 Tree {
317 id: ObjectId,
318 },
319 Parent {
320 id: ObjectId,
321 },
322 Author {
324 signature: gix_actor::SignatureRef<'a>,
325 },
326 Committer {
328 signature: gix_actor::SignatureRef<'a>,
329 },
330 Encoding(&'a BStr),
331 ExtraHeader((&'a BStr, Cow<'a, BStr>)),
332 Message(&'a BStr),
333}
334
335impl Token<'_> {
336 pub fn id(&self) -> Option<&oid> {
338 match self {
339 Token::Tree { id } | Token::Parent { id } => Some(id.as_ref()),
340 _ => None,
341 }
342 }
343
344 pub fn try_into_id(self) -> Option<ObjectId> {
346 match self {
347 Token::Tree { id } | Token::Parent { id } => Some(id),
348 _ => None,
349 }
350 }
351}