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