1use std::ffi::CString;
2use std::marker;
3use std::mem;
4use std::ptr;
5use std::str;
6
7use crate::util::Binding;
8use crate::{call, raw, signature, Error, Object, ObjectType, Oid, Signature};
9
10pub struct Tag<'repo> {
14 raw: *mut raw::git_tag,
15 _marker: marker::PhantomData<Object<'repo>>,
16}
17
18impl<'repo> Tag<'repo> {
19 pub fn is_valid_name(tag_name: &str) -> bool {
23 crate::init();
24 let tag_name = CString::new(tag_name).unwrap();
25 let mut valid: libc::c_int = 0;
26 unsafe {
27 call::c_try(raw::git_tag_name_is_valid(&mut valid, tag_name.as_ptr())).unwrap();
28 }
29 valid == 1
30 }
31
32 pub fn id(&self) -> Oid {
34 unsafe { Binding::from_raw(raw::git_tag_id(&*self.raw)) }
35 }
36
37 pub fn message(&self) -> Option<&str> {
41 self.message_bytes().and_then(|s| str::from_utf8(s).ok())
42 }
43
44 pub fn message_bytes(&self) -> Option<&[u8]> {
48 unsafe { crate::opt_bytes(self, raw::git_tag_message(&*self.raw)) }
49 }
50
51 pub fn name(&self) -> Option<&str> {
55 str::from_utf8(self.name_bytes()).ok()
56 }
57
58 pub fn name_bytes(&self) -> &[u8] {
60 unsafe { crate::opt_bytes(self, raw::git_tag_name(&*self.raw)).unwrap() }
61 }
62
63 pub fn peel(&self) -> Result<Object<'repo>, Error> {
65 let mut ret = ptr::null_mut();
66 unsafe {
67 try_call!(raw::git_tag_peel(&mut ret, &*self.raw));
68 Ok(Binding::from_raw(ret))
69 }
70 }
71
72 pub fn tagger(&self) -> Option<Signature<'_>> {
76 unsafe {
77 let ptr = raw::git_tag_tagger(&*self.raw);
78 if ptr.is_null() {
79 None
80 } else {
81 Some(signature::from_raw_const(self, ptr))
82 }
83 }
84 }
85
86 pub fn target(&self) -> Result<Object<'repo>, Error> {
91 let mut ret = ptr::null_mut();
92 unsafe {
93 try_call!(raw::git_tag_target(&mut ret, &*self.raw));
94 Ok(Binding::from_raw(ret))
95 }
96 }
97
98 pub fn target_id(&self) -> Oid {
100 unsafe { Binding::from_raw(raw::git_tag_target_id(&*self.raw)) }
101 }
102
103 pub fn target_type(&self) -> Option<ObjectType> {
105 unsafe { ObjectType::from_raw(raw::git_tag_target_type(&*self.raw)) }
106 }
107
108 pub fn as_object(&self) -> &Object<'repo> {
110 unsafe { &*(self as *const _ as *const Object<'repo>) }
111 }
112
113 pub fn into_object(self) -> Object<'repo> {
115 assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
116 unsafe { mem::transmute(self) }
117 }
118}
119
120impl<'repo> std::fmt::Debug for Tag<'repo> {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
122 let mut ds = f.debug_struct("Tag");
123 if let Some(name) = self.name() {
124 ds.field("name", &name);
125 }
126 ds.field("id", &self.id());
127 ds.finish()
128 }
129}
130
131impl<'repo> Binding for Tag<'repo> {
132 type Raw = *mut raw::git_tag;
133 unsafe fn from_raw(raw: *mut raw::git_tag) -> Tag<'repo> {
134 Tag {
135 raw,
136 _marker: marker::PhantomData,
137 }
138 }
139 fn raw(&self) -> *mut raw::git_tag {
140 self.raw
141 }
142}
143
144impl<'repo> Clone for Tag<'repo> {
145 fn clone(&self) -> Self {
146 self.as_object().clone().into_tag().ok().unwrap()
147 }
148}
149
150impl<'repo> Drop for Tag<'repo> {
151 fn drop(&mut self) {
152 unsafe { raw::git_tag_free(self.raw) }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use crate::Tag;
159
160 #[test]
162 fn name_is_valid() {
163 assert_eq!(Tag::is_valid_name("blah_blah"), true);
164 assert_eq!(Tag::is_valid_name("v1.2.3"), true);
165 assert_eq!(Tag::is_valid_name("my/tag"), true);
166 assert_eq!(Tag::is_valid_name("@"), true);
167
168 assert_eq!(Tag::is_valid_name("-foo"), false);
169 assert_eq!(Tag::is_valid_name("foo:bar"), false);
170 assert_eq!(Tag::is_valid_name("foo^bar"), false);
171 assert_eq!(Tag::is_valid_name("foo."), false);
172 assert_eq!(Tag::is_valid_name("@{"), false);
173 assert_eq!(Tag::is_valid_name("as\\cd"), false);
174 }
175
176 #[test]
177 #[should_panic]
178 fn is_valid_name_for_invalid_tag() {
179 Tag::is_valid_name("ab\012");
180 }
181
182 #[test]
183 fn smoke() {
184 let (_td, repo) = crate::test::repo_init();
185 let head = repo.head().unwrap();
186 let id = head.target().unwrap();
187 assert!(repo.find_tag(id).is_err());
188
189 let obj = repo.find_object(id, None).unwrap();
190 let sig = repo.signature().unwrap();
191 let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap();
192 let tag = repo.find_tag(tag_id).unwrap();
193 assert_eq!(tag.id(), tag_id);
194
195 let tags = repo.tag_names(None).unwrap();
196 assert_eq!(tags.len(), 1);
197 assert_eq!(tags.get(0), Some("foo"));
198
199 assert_eq!(tag.name(), Some("foo"));
200 assert_eq!(tag.message(), Some("msg"));
201 assert_eq!(tag.peel().unwrap().id(), obj.id());
202 assert_eq!(tag.target_id(), obj.id());
203 assert_eq!(tag.target_type(), Some(crate::ObjectType::Commit));
204
205 assert_eq!(tag.tagger().unwrap().name(), sig.name());
206 tag.target().unwrap();
207 tag.into_object();
208
209 repo.find_object(tag_id, None).unwrap().as_tag().unwrap();
210 repo.find_object(tag_id, None)
211 .unwrap()
212 .into_tag()
213 .ok()
214 .unwrap();
215
216 repo.tag_delete("foo").unwrap();
217 }
218
219 #[test]
220 fn lite() {
221 let (_td, repo) = crate::test::repo_init();
222 let head = t!(repo.head());
223 let id = head.target().unwrap();
224 let obj = t!(repo.find_object(id, None));
225 let tag_id = t!(repo.tag_lightweight("foo", &obj, false));
226 assert!(repo.find_tag(tag_id).is_err());
227 assert_eq!(t!(repo.refname_to_id("refs/tags/foo")), id);
228
229 let tags = t!(repo.tag_names(Some("f*")));
230 assert_eq!(tags.len(), 1);
231 let tags = t!(repo.tag_names(Some("b*")));
232 assert_eq!(tags.len(), 0);
233 }
234}