1use std::io;
2
3use bstr::BStr;
4use gix_date::parse::TimeBuf;
5
6use crate::{encode, encode::NL, Kind, Tag, TagRef};
7
8#[derive(Debug, thiserror::Error)]
10#[allow(missing_docs)]
11pub enum Error {
12 #[error("Tags must not start with a dash: '-'")]
13 StartsWithDash,
14 #[error("The tag name was no valid reference name")]
15 InvalidRefName(#[from] gix_validate::tag::name::Error),
16}
17
18impl From<Error> for io::Error {
19 fn from(err: Error) -> Self {
20 io::Error::other(err)
21 }
22}
23
24impl crate::WriteTo for Tag {
25 fn write_to(&self, out: &mut dyn io::Write) -> io::Result<()> {
26 encode::trusted_header_id(b"object", &self.target, out)?;
27 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), out)?;
28 encode::header_field(b"tag", validated_name(self.name.as_ref())?, out)?;
29 if let Some(tagger) = &self.tagger {
30 let mut buf = TimeBuf::default();
31 encode::trusted_header_signature(b"tagger", &tagger.to_ref(&mut buf), out)?;
32 }
33
34 if !self.message.iter().all(|b| *b == b'\n') {
35 out.write_all(NL)?;
36 }
37 out.write_all(self.message.as_ref())?;
38 if let Some(message) = &self.pgp_signature {
39 out.write_all(NL)?;
40 out.write_all(message.as_ref())?;
41 }
42 Ok(())
43 }
44
45 fn kind(&self) -> Kind {
46 Kind::Tag
47 }
48
49 fn size(&self) -> u64 {
50 (b"object".len() + 1 + self.target.kind().len_in_hex() + 1 + b"type".len() + 1 + self.target_kind.as_bytes().len() + 1 + b"tag".len() + 1 + self.name.len() + 1 + self
54 .tagger
55 .as_ref()
56 .map_or(0, |t| b"tagger".len() + 1 + t.size() + 1 )
57 + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 } + self.message.len()
58 + self.pgp_signature.as_ref().map_or(0, |m| 1 + m.len())) as u64
59 }
60}
61
62impl crate::WriteTo for TagRef<'_> {
63 fn write_to(&self, mut out: &mut dyn io::Write) -> io::Result<()> {
64 encode::trusted_header_field(b"object", self.target, &mut out)?;
65 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), &mut out)?;
66 encode::header_field(b"tag", validated_name(self.name)?, &mut out)?;
67 if let Some(tagger) = &self.tagger {
68 encode::trusted_header_signature(b"tagger", tagger, &mut out)?;
69 }
70
71 if !self.message.iter().all(|b| *b == b'\n') {
72 out.write_all(NL)?;
73 }
74 out.write_all(self.message)?;
75 if let Some(message) = self.pgp_signature {
76 out.write_all(NL)?;
77 out.write_all(message)?;
78 }
79 Ok(())
80 }
81
82 fn kind(&self) -> Kind {
83 Kind::Tag
84 }
85
86 fn size(&self) -> u64 {
87 (b"object".len() + 1 + self.target().kind().len_in_hex() + 1 + b"type".len() + 1 + self.target_kind.as_bytes().len() + 1 + b"tag".len() + 1 + self.name.len() + 1 + self
91 .tagger
92 .as_ref()
93 .map_or(0, |t| b"tagger".len() + 1 + t.size() + 1 )
94 + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 } + self.message.len()
95 + self.pgp_signature.as_ref().map_or(0, |m| 1 + m.len())) as u64
96 }
97}
98
99fn validated_name(name: &BStr) -> Result<&BStr, Error> {
100 gix_validate::tag::name(name)?;
101 if name[0] == b'-' {
102 return Err(Error::StartsWithDash);
103 }
104 Ok(name)
105}
106
107#[cfg(test)]
108mod tests;