1use std::io;
2
3use bstr::BStr;
4use gix_date::parse::TimeBuf;
5use gix_error::{Result, ResultExt, ensure, 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(io::Error::other)?,
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(b"tag", validated_name(self.name).map_err(io::Error::other)?, &mut out)?;
56 if let Some(tagger) = self.tagger {
57 encode::trusted_header_field(b"tagger", tagger.as_ref(), &mut out)?;
58 }
59
60 if !self.message.iter().all(|b| *b == b'\n') {
61 out.write_all(NL)?;
62 }
63 out.write_all(self.message)?;
64 if let Some(message) = self.signature {
65 out.write_all(NL)?;
66 out.write_all(message)?;
67 }
68 Ok(())
69 }
70
71 fn kind(&self) -> Kind {
72 Kind::Tag
73 }
74
75 fn size(&self) -> u64 {
76 (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
80 .tagger
81 .map_or(0, |raw| b"tagger".len() + 1 + raw.len() + 1 )
82 + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 } + self.message.len()
83 + self.signature.as_ref().map_or(0, |m| 1 + m.len())) as u64
84 }
85}
86
87fn validated_name(name: &BStr) -> Result<&BStr> {
88 gix_validate::tag::name(name).or_raise(|| validation("The tag name was no valid reference name"))?;
89 ensure!(name[0] != b'-', validation("Tags must not start with a dash: '-'"));
90 Ok(name)
91}
92
93#[cfg(test)]
94mod tests {
95 mod validated_name {
96 mod invalid {
97 use bstr::ByteSlice;
98
99 use super::super::super::*;
100
101 #[test]
102 fn only_dash() {
103 assert!(validated_name(b"-".as_bstr()).is_err());
104 }
105 #[test]
106 fn leading_dash() {
107 assert!(validated_name(b"-hello".as_bstr()).is_err());
108 }
109 }
110
111 mod valid {
112 use bstr::ByteSlice;
113
114 use super::super::super::*;
115
116 #[test]
117 fn version() {
118 for version in &["v1.0.0", "0.2.1", "0-alpha1"] {
119 assert!(validated_name(version.as_bytes().as_bstr()).is_ok());
120 }
121 }
122 }
123 }
124}