1use std::io;
2
3use bstr::BStr;
4use gix_date::parse::TimeBuf;
5use gix_error::{ErrorExt, ExnMessageResult, ResultExt, validation};
6
7use crate::{Kind, Tag, TagRef, encode, encode::NL};
8
9impl crate::WriteTo for Tag {
10 fn write_to(&self, out: &mut dyn io::Write) -> io::Result<()> {
11 encode::trusted_header_id(b"object", &self.target, out)?;
12 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), out)?;
13 encode::header_field(
14 b"tag",
15 validated_name(self.name.as_ref()).map_err(|err| io::Error::other(err.into_error()))?,
16 out,
17 )?;
18 if let Some(tagger) = &self.tagger {
19 let mut buf = TimeBuf::default();
20 encode::trusted_header_signature(b"tagger", &tagger.to_ref(&mut buf), out)?;
21 }
22
23 if !self.message.iter().all(|b| *b == b'\n') {
24 out.write_all(NL)?;
25 }
26 out.write_all(self.message.as_ref())?;
27 if let Some(message) = &self.signature {
28 out.write_all(NL)?;
29 out.write_all(message.as_ref())?;
30 }
31 Ok(())
32 }
33
34 fn kind(&self) -> Kind {
35 Kind::Tag
36 }
37
38 fn size(&self) -> u64 {
39 (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
43 .tagger
44 .as_ref()
45 .map_or(0, |t| b"tagger".len() + 1 + t.size() + 1 )
46 + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 } + self.message.len()
47 + self.signature.as_ref().map_or(0, |m| 1 + m.len())) as u64
48 }
49}
50
51impl crate::WriteTo for TagRef<'_> {
52 fn write_to(&self, mut out: &mut dyn io::Write) -> io::Result<()> {
53 encode::trusted_header_field(b"object", self.target, &mut out)?;
54 encode::trusted_header_field(b"type", self.target_kind.as_bytes(), &mut out)?;
55 encode::header_field(
56 b"tag",
57 validated_name(self.name).map_err(|err| io::Error::other(err.into_error()))?,
58 &mut out,
59 )?;
60 if let Some(tagger) = self.tagger {
61 encode::trusted_header_field(b"tagger", tagger.as_ref(), &mut out)?;
62 }
63
64 if !self.message.iter().all(|b| *b == b'\n') {
65 out.write_all(NL)?;
66 }
67 out.write_all(self.message)?;
68 if let Some(message) = self.signature {
69 out.write_all(NL)?;
70 out.write_all(message)?;
71 }
72 Ok(())
73 }
74
75 fn kind(&self) -> Kind {
76 Kind::Tag
77 }
78
79 fn size(&self) -> u64 {
80 (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
84 .tagger
85 .map_or(0, |raw| b"tagger".len() + 1 + raw.len() + 1 )
86 + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 } + self.message.len()
87 + self.signature.as_ref().map_or(0, |m| 1 + m.len())) as u64
88 }
89}
90
91fn validated_name(name: &BStr) -> ExnMessageResult<&BStr> {
92 gix_validate::tag::name(name).or_raise(|| validation("The tag name was no valid reference name"))?;
93 if name[0] == b'-' {
94 return Err(validation("Tags must not start with a dash: '-'").raise());
95 }
96 Ok(name)
97}
98
99#[cfg(test)]
100mod tests {
101 mod validated_name {
102 mod invalid {
103 use bstr::ByteSlice;
104
105 use super::super::super::*;
106
107 #[test]
108 fn only_dash() {
109 assert!(validated_name(b"-".as_bstr()).is_err());
110 }
111 #[test]
112 fn leading_dash() {
113 assert!(validated_name(b"-hello".as_bstr()).is_err());
114 }
115 }
116
117 mod valid {
118 use bstr::ByteSlice;
119
120 use super::super::super::*;
121
122 #[test]
123 fn version() {
124 for version in &["v1.0.0", "0.2.1", "0-alpha1"] {
125 assert!(validated_name(version.as_bytes().as_bstr()).is_ok());
126 }
127 }
128 }
129 }
130}