Skip to main content

gix_object/tag/
write.rs

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 /* space */ + self.target.kind().len_in_hex() + 1 /* nl */
40            + b"type".len() + 1 /* space */ + self.target_kind.as_bytes().len() + 1 /* nl */
41            + b"tag".len() + 1 /* space */ + self.name.len() + 1 /* nl */
42            + self
43            .tagger
44            .as_ref()
45            .map_or(0, |t| b"tagger".len() + 1 /* space */ + t.size() + 1 /* nl */)
46            + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
47            + self.signature.as_ref().map_or(0, |m| 1 /* nl */ + 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 /* space */ + self.target().kind().len_in_hex() + 1 /* nl */
77            + b"type".len() + 1 /* space */ + self.target_kind.as_bytes().len() + 1 /* nl */
78            + b"tag".len() + 1 /* space */ + self.name.len() + 1 /* nl */
79            + self
80                .tagger
81                .map_or(0, |raw| b"tagger".len() + 1 /* space */ + raw.len() + 1 /* nl */)
82            + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
83            + self.signature.as_ref().map_or(0, |m| 1 /* nl */ + 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}