1use bstr::{BStr, ByteSlice};
2
3use crate::parse::parse_signature;
4use crate::{Commit, CommitRef, TagRef};
5
6pub const SIGNATURE_FIELD_NAME: &str = "gpgsig";
8pub const SIGNATURE_FIELD_NAME_SHA256: &str = "gpgsig-sha256";
10
11pub fn signature_field_name(hash_kind: gix_hash::Kind) -> &'static str {
13 #[cfg(feature = "sha256")]
14 if hash_kind == gix_hash::Kind::Sha256 {
15 return SIGNATURE_FIELD_NAME_SHA256;
16 }
17 let _ = hash_kind;
18 SIGNATURE_FIELD_NAME
19}
20
21mod decode;
22pub mod message;
24
25#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct MessageRef<'a> {
31 #[cfg_attr(feature = "serde", serde(borrow))]
33 pub title: &'a BStr,
34 pub body: Option<&'a BStr>,
38}
39
40pub mod ref_iter;
42
43mod write;
44
45impl<'a> CommitRef<'a> {
47 pub fn from_bytes(mut data: &'a [u8], object_hash: gix_hash::Kind) -> Result<CommitRef<'a>, crate::decode::Error> {
50 let input = &mut data;
51 match decode::commit(input, object_hash) {
52 Ok(tag) => Ok(tag),
53 Err(err) => Err(err),
54 }
55 }
56}
57
58impl<'a> CommitRef<'a> {
60 pub fn tree(&self) -> gix_hash::ObjectId {
62 gix_hash::ObjectId::from_hex(self.tree).expect("prior validation of tree hash during parsing")
63 }
64
65 pub fn parents(&self) -> impl Iterator<Item = gix_hash::ObjectId> + '_ {
67 self.parents
68 .iter()
69 .map(|hex_hash| gix_hash::ObjectId::from_hex(hex_hash).expect("prior validation of hashes during parsing"))
70 }
71
72 pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
74 ExtraHeaders::new(
75 self.extra_headers.iter().map(|(k, v)| (*k, v.as_ref())),
76 self.tree().kind(),
77 )
78 }
79
80 pub fn author(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
84 parse_signature(self.author).map(|signature| signature.trim())
85 }
86
87 pub fn committer(&self) -> Result<gix_actor::SignatureRef<'a>, crate::decode::Error> {
91 parse_signature(self.committer).map(|signature| signature.trim())
92 }
93
94 pub fn message(&self) -> MessageRef<'a> {
96 MessageRef::from_bytes(self.message)
97 }
98
99 pub fn time(&self) -> Result<gix_date::Time, crate::decode::Error> {
101 parse_signature(self.committer).map(|signature| signature.time().unwrap_or_default())
102 }
103}
104
105impl CommitRef<'_> {
107 pub fn into_owned(self) -> Result<Commit, crate::decode::Error> {
109 self.try_into()
110 }
111
112 pub fn to_owned(self) -> Result<Commit, crate::decode::Error> {
114 self.try_into()
115 }
116}
117
118impl Commit {
119 pub fn extra_headers(&self) -> ExtraHeaders<impl Iterator<Item = (&BStr, &BStr)>> {
121 ExtraHeaders::new(
122 self.extra_headers.iter().map(|(k, v)| (k.as_bstr(), v.as_bstr())),
123 self.tree.kind(),
124 )
125 }
126}
127
128pub struct ExtraHeaders<I> {
130 inner: I,
131 hash_kind: gix_hash::Kind,
132}
133
134impl<'a, I> ExtraHeaders<I>
136where
137 I: Iterator<Item = (&'a BStr, &'a BStr)>,
138{
139 pub fn new(iter: I, hash_kind: gix_hash::Kind) -> Self {
141 ExtraHeaders { inner: iter, hash_kind }
142 }
143
144 pub fn find(mut self, name: &str) -> Option<&'a BStr> {
146 self.inner
147 .find_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
148 }
149
150 pub fn find_pos(self, name: &str) -> Option<usize> {
152 self.inner
153 .enumerate()
154 .find_map(|(pos, (field, _value))| (field == name).then_some(pos))
155 }
156
157 pub fn find_all(self, name: &'a str) -> impl Iterator<Item = &'a BStr> {
159 self.inner
160 .filter_map(move |(k, v)| if k == name.as_bytes().as_bstr() { Some(v) } else { None })
161 }
162
163 pub fn mergetags(self) -> impl Iterator<Item = Result<TagRef<'a>, crate::decode::Error>> {
168 let hash_kind = self.hash_kind;
169 self.find_all("mergetag").map(move |b| TagRef::from_bytes(b, hash_kind))
170 }
171
172 pub fn pgp_signature(self) -> Option<&'a BStr> {
174 let field_name = signature_field_name(self.hash_kind);
175 self.find(field_name)
176 }
177}