Skip to main content

gix_object/tag/
write.rs

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 /* 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(
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 /* space */ + self.target().kind().len_in_hex() + 1 /* nl */
81            + b"type".len() + 1 /* space */ + self.target_kind.as_bytes().len() + 1 /* nl */
82            + b"tag".len() + 1 /* space */ + self.name.len() + 1 /* nl */
83            + self
84                .tagger
85                .map_or(0, |raw| b"tagger".len() + 1 /* space */ + raw.len() + 1 /* nl */)
86            + if self.message.iter().all(|b| *b == b'\n') { 0 } else { 1 /* nl */ } + self.message.len()
87            + self.signature.as_ref().map_or(0, |m| 1 /* nl */ + 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}