1use gix_error::{OptionExt, Result};
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(data: &'a [u8], hash_kind: gix_hash::Kind) -> Result<Option<(Cow<'a, BStr>, SignedData<'a>)>> {
55 let mut signature_and_range = None;
56
57 let raw_tokens = CommitRefIterRaw {
58 data,
59 state: State::default(),
60 offset: 0,
61 hash_kind,
62 };
63 for token in raw_tokens {
64 let token = token?;
65 if let Token::ExtraHeader((name, value)) = &token.token
66 && *name == signature_field_name(hash_kind)
67 {
68 signature_and_range = Some((value.clone(), token.token_range));
71 break;
72 }
73 }
74
75 Ok(signature_and_range.map(|(sig, signature_range)| (sig, SignedData::new(data, signature_range))))
76 }
77
78 pub fn tree_id(&mut self) -> Result<ObjectId> {
86 let tree_id = self.next().ok_or_raise(missing_field)??;
87 Token::try_into_id(tree_id).ok_or_raise(missing_field)
88 }
89
90 pub fn parent_ids(self) -> impl Iterator<Item = gix_hash::ObjectId> + 'a {
94 self.filter_map(|t| match t {
95 Ok(Token::Parent { id }) => Some(id),
96 _ => None,
97 })
98 }
99
100 pub fn signatures(self) -> impl Iterator<Item = gix_actor::SignatureRef<'a>> + 'a {
106 self.filter_map(|t| match t {
107 Ok(Token::Author { signature } | Token::Committer { signature }) => Some(signature),
108 _ => None,
109 })
110 }
111
112 pub fn committer(mut self) -> Result<gix_actor::SignatureRef<'a>> {
114 self.find_map(|t| match t {
115 Ok(Token::Committer { signature }) => Some(Ok(signature)),
116 Err(err) => Some(Err(err)),
117 _ => None,
118 })
119 .ok_or_raise(missing_field)?
120 }
121
122 pub fn author(mut self) -> Result<gix_actor::SignatureRef<'a>> {
126 self.find_map(|t| match t {
127 Ok(Token::Author { signature }) => Some(Ok(signature)),
128 Err(err) => Some(Err(err)),
129 _ => None,
130 })
131 .ok_or_raise(missing_field)?
132 }
133
134 pub fn message(mut self) -> Result<&'a BStr> {
139 self.find_map(|t| match t {
140 Ok(Token::Message(msg)) => Some(Ok(msg)),
141 Err(err) => Some(Err(err)),
142 _ => None,
143 })
144 .transpose()
145 .map(Option::unwrap_or_default)
146 }
147}
148
149fn missing_field() -> gix_error::Message {
150 crate::decode::empty_error()
151}
152
153impl<'a> CommitRefIter<'a> {
154 #[inline]
155 fn next_inner(mut i: &'a [u8], state: &mut State, hash_kind: gix_hash::Kind) -> Result<(&'a [u8], Token<'a>)> {
156 let input = &mut i;
157 match Self::next_inner_(input, state, hash_kind) {
158 Ok(token) => Ok((*input, token)),
159 Err(err) => Err(err),
160 }
161 }
162
163 fn next_inner_(input: &mut &'a [u8], state: &mut State, hash_kind: gix_hash::Kind) -> Result<Token<'a>> {
164 use State::*;
165 Ok(match state {
166 Tree => {
167 let tree = parse::header_field(input, b"tree", |value| parse::hex_hash(value, hash_kind))?;
168 *state = State::Parents;
169 Token::Tree {
170 id: ObjectId::from_hex(tree).expect("parsing validation"),
171 }
172 }
173 Parents => {
174 if input.starts_with(b"parent ") {
175 let parent = parse::header_field(input, b"parent", |value| parse::hex_hash(value, hash_kind))?;
176 Token::Parent {
177 id: ObjectId::from_hex(parent).expect("parsing validation"),
178 }
179 } else {
180 *state = State::Signature {
181 of: SignatureKind::Author,
182 };
183 Self::next_inner_(input, state, hash_kind)?
184 }
185 }
186 Signature { of } => {
187 let who = *of;
188 let field_name = match of {
189 SignatureKind::Author => {
190 *of = SignatureKind::Committer;
191 &b"author"[..]
192 }
193 SignatureKind::Committer => {
194 *state = State::Encoding;
195 &b"committer"[..]
196 }
197 };
198 let signature = parse::header_field(input, field_name, parse::signature)?;
199 match who {
200 SignatureKind::Author => Token::Author { signature },
201 SignatureKind::Committer => Token::Committer { signature },
202 }
203 }
204 Encoding => {
205 *state = State::ExtraHeaders;
206 if input.starts_with(b"encoding ") {
207 let encoding = parse::header_field(input, b"encoding", Ok)?;
208 Token::Encoding(encoding.as_bstr())
209 } else {
210 Self::next_inner_(input, state, hash_kind)?
211 }
212 }
213 ExtraHeaders => {
214 if input.starts_with(b"\n") {
215 *state = State::Message;
216 Self::next_inner_(input, state, hash_kind)?
217 } else {
218 let before = *input;
219 {
220 let extra_header = parse::any_header_field_multi_line(input)
221 .map(|(k, o)| (k.as_bstr(), Cow::Owned(o)))
222 .or_else(|_| {
223 *input = before;
224 parse::any_header_field(input).map(|(k, o)| (k.as_bstr(), Cow::Borrowed(o.as_bstr())))
225 })?;
226 Token::ExtraHeader(extra_header)
227 }
228 }
229 }
230 Message => {
231 let message = decode::message(input)?;
232 debug_assert!(
233 input.is_empty(),
234 "we should have consumed all data - otherwise iter may go forever"
235 );
236 Token::Message(message)
237 }
238 })
239 }
240}
241
242impl<'a> Iterator for CommitRefIter<'a> {
243 type Item = Result<Token<'a>>;
244
245 fn next(&mut self) -> Option<Self::Item> {
246 if self.data.is_empty() {
247 return None;
248 }
249 match Self::next_inner(self.data, &mut self.state, self.hash_kind) {
250 Ok((data, token)) => {
251 self.data = data;
252 Some(Ok(token))
253 }
254 Err(err) => {
255 self.data = &[];
256 Some(Err(err))
257 }
258 }
259 }
260}
261
262struct CommitRefIterRaw<'a> {
264 data: &'a [u8],
265 state: State,
266 offset: usize,
267 hash_kind: gix_hash::Kind,
268}
269
270impl<'a> Iterator for CommitRefIterRaw<'a> {
271 type Item = Result<RawToken<'a>>;
272
273 fn next(&mut self) -> Option<Self::Item> {
274 if self.data.is_empty() {
275 return None;
276 }
277 match CommitRefIter::next_inner(self.data, &mut self.state, self.hash_kind) {
278 Ok((remaining, token)) => {
279 let consumed = self.data.len() - remaining.len();
280 let start = self.offset;
281 let end = start + consumed;
282 self.offset = end;
283
284 self.data = remaining;
285 Some(Ok(RawToken {
286 token,
287 token_range: start..end,
288 }))
289 }
290 Err(err) => {
291 self.data = &[];
292 Some(Err(err))
293 }
294 }
295 }
296}
297
298struct RawToken<'a> {
300 token: Token<'a>,
302 token_range: Range<usize>,
303}
304
305#[expect(missing_docs)]
307#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
308pub enum Token<'a> {
309 Tree {
310 id: ObjectId,
311 },
312 Parent {
313 id: ObjectId,
314 },
315 Author {
317 signature: gix_actor::SignatureRef<'a>,
318 },
319 Committer {
321 signature: gix_actor::SignatureRef<'a>,
322 },
323 Encoding(&'a BStr),
324 ExtraHeader((&'a BStr, Cow<'a, BStr>)),
325 Message(&'a BStr),
326}
327
328impl Token<'_> {
329 pub fn id(&self) -> Option<&oid> {
331 match self {
332 Token::Tree { id } | Token::Parent { id } => Some(id.as_ref()),
333 _ => None,
334 }
335 }
336
337 pub fn try_into_id(self) -> Option<ObjectId> {
339 match self {
340 Token::Tree { id } | Token::Parent { id } => Some(id),
341 _ => None,
342 }
343 }
344}