1use std::io;
2
3use bstr::BStr;
4
5use crate::{encode, encode::NL, Kind, Tag, TagRef};
6
7#[derive(Debug, thiserror::Error)]
9#[allow(missing_docs)]
10pub enum Error {
11 #[error("Tags must not start with a dash: '-'")]
12 StartsWithDash,
13 #[error("The tag name was no valid reference name")]
14 InvalidRefName(#[from] git_validate::tag::name::Error),
15}
16
17impl From<Error> for io::Error {
18 fn from(err: Error) -> Self {
19 io::Error::new(io::ErrorKind::Other, err)
20 }
21}
22
23impl crate::WriteTo for Tag {
24 fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
25 encode::trusted_header_id(b"object", &self.target, &mut out)?;
26 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), &mut out)?;
27 encode::header_field(b"tag", validated_name(self.name.as_ref())?, &mut out)?;
28 if let Some(tagger) = &self.tagger {
29 encode::trusted_header_signature(b"tagger", &tagger.to_ref(), &mut out)?;
30 }
31
32 out.write_all(NL)?;
33 if !self.message.is_empty() {
34 out.write_all(self.message.as_ref())?;
35 }
36 if let Some(message) = &self.pgp_signature {
37 out.write_all(NL)?;
38 out.write_all(message.as_ref())?;
39 }
40 Ok(())
41 }
42
43 fn kind(&self) -> Kind {
44 Kind::Tag
45 }
46
47 fn size(&self) -> usize {
48 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
52 .tagger
53 .as_ref()
54 .map(|t| b"tagger".len() + 1 + t.size() + 1 )
55 .unwrap_or(0)
56 + 1 + self.message.len()
57 + self.pgp_signature.as_ref().map(|m| 1 + m.len()).unwrap_or(0)
58 }
59}
60
61impl<'a> crate::WriteTo for TagRef<'a> {
62 fn write_to(&self, mut out: impl io::Write) -> io::Result<()> {
63 encode::trusted_header_field(b"object", self.target, &mut out)?;
64 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), &mut out)?;
65 encode::header_field(b"tag", validated_name(self.name)?, &mut out)?;
66 if let Some(tagger) = &self.tagger {
67 encode::trusted_header_signature(b"tagger", tagger, &mut out)?;
68 }
69
70 out.write_all(NL)?;
71 if !self.message.is_empty() {
72 out.write_all(self.message)?;
73 }
74 if let Some(message) = self.pgp_signature {
75 out.write_all(NL)?;
76 out.write_all(message)?;
77 }
78 Ok(())
79 }
80
81 fn kind(&self) -> Kind {
82 Kind::Tag
83 }
84
85 fn size(&self) -> usize {
86 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
90 .tagger
91 .as_ref()
92 .map(|t| b"tagger".len() + 1 + t.size() + 1 )
93 .unwrap_or(0)
94 + 1 + self.message.len()
95 + self.pgp_signature.as_ref().map(|m| 1 + m.len()).unwrap_or(0)
96 }
97}
98
99fn validated_name(name: &BStr) -> Result<&BStr, Error> {
100 git_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;