1 use std::marker;
2 use std::mem;
3 use std::ptr;
4 use std::str;
5 
6 use crate::util::Binding;
7 use crate::{raw, signature, Error, Object, ObjectType, Oid, Signature};
8 
9 /// A structure to represent a git [tag][1]
10 ///
11 /// [1]: http://git-scm.com/book/en/Git-Basics-Tagging
12 pub struct Tag<'repo> {
13     raw: *mut raw::git_tag,
14     _marker: marker::PhantomData<Object<'repo>>,
15 }
16 
17 impl<'repo> Tag<'repo> {
18     /// Get the id (SHA1) of a repository tag
id(&self) -> Oid19     pub fn id(&self) -> Oid {
20         unsafe { Binding::from_raw(raw::git_tag_id(&*self.raw)) }
21     }
22 
23     /// Get the message of a tag
24     ///
25     /// Returns None if there is no message or if it is not valid utf8
message(&self) -> Option<&str>26     pub fn message(&self) -> Option<&str> {
27         self.message_bytes().and_then(|s| str::from_utf8(s).ok())
28     }
29 
30     /// Get the message of a tag
31     ///
32     /// Returns None if there is no message
message_bytes(&self) -> Option<&[u8]>33     pub fn message_bytes(&self) -> Option<&[u8]> {
34         unsafe { crate::opt_bytes(self, raw::git_tag_message(&*self.raw)) }
35     }
36 
37     /// Get the name of a tag
38     ///
39     /// Returns None if it is not valid utf8
name(&self) -> Option<&str>40     pub fn name(&self) -> Option<&str> {
41         str::from_utf8(self.name_bytes()).ok()
42     }
43 
44     /// Get the name of a tag
name_bytes(&self) -> &[u8]45     pub fn name_bytes(&self) -> &[u8] {
46         unsafe { crate::opt_bytes(self, raw::git_tag_name(&*self.raw)).unwrap() }
47     }
48 
49     /// Recursively peel a tag until a non tag git_object is found
peel(&self) -> Result<Object<'repo>, Error>50     pub fn peel(&self) -> Result<Object<'repo>, Error> {
51         let mut ret = ptr::null_mut();
52         unsafe {
53             try_call!(raw::git_tag_peel(&mut ret, &*self.raw));
54             Ok(Binding::from_raw(ret))
55         }
56     }
57 
58     /// Get the tagger (author) of a tag
59     ///
60     /// If the author is unspecified, then `None` is returned.
tagger(&self) -> Option<Signature<'_>>61     pub fn tagger(&self) -> Option<Signature<'_>> {
62         unsafe {
63             let ptr = raw::git_tag_tagger(&*self.raw);
64             if ptr.is_null() {
65                 None
66             } else {
67                 Some(signature::from_raw_const(self, ptr))
68             }
69         }
70     }
71 
72     /// Get the tagged object of a tag
73     ///
74     /// This method performs a repository lookup for the given object and
75     /// returns it
target(&self) -> Result<Object<'repo>, Error>76     pub fn target(&self) -> Result<Object<'repo>, Error> {
77         let mut ret = ptr::null_mut();
78         unsafe {
79             try_call!(raw::git_tag_target(&mut ret, &*self.raw));
80             Ok(Binding::from_raw(ret))
81         }
82     }
83 
84     /// Get the OID of the tagged object of a tag
target_id(&self) -> Oid85     pub fn target_id(&self) -> Oid {
86         unsafe { Binding::from_raw(raw::git_tag_target_id(&*self.raw)) }
87     }
88 
89     /// Get the ObjectType of the tagged object of a tag
target_type(&self) -> Option<ObjectType>90     pub fn target_type(&self) -> Option<ObjectType> {
91         unsafe { ObjectType::from_raw(raw::git_tag_target_type(&*self.raw)) }
92     }
93 
94     /// Casts this Tag to be usable as an `Object`
as_object(&self) -> &Object<'repo>95     pub fn as_object(&self) -> &Object<'repo> {
96         unsafe { &*(self as *const _ as *const Object<'repo>) }
97     }
98 
99     /// Consumes Tag to be returned as an `Object`
into_object(self) -> Object<'repo>100     pub fn into_object(self) -> Object<'repo> {
101         assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
102         unsafe { mem::transmute(self) }
103     }
104 }
105 
106 impl<'repo> std::fmt::Debug for Tag<'repo> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>107     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
108         let mut ds = f.debug_struct("Tag");
109         if let Some(name) = self.name() {
110             ds.field("name", &name);
111         }
112         ds.field("id", &self.id());
113         ds.finish()
114     }
115 }
116 
117 impl<'repo> Binding for Tag<'repo> {
118     type Raw = *mut raw::git_tag;
from_raw(raw: *mut raw::git_tag) -> Tag<'repo>119     unsafe fn from_raw(raw: *mut raw::git_tag) -> Tag<'repo> {
120         Tag {
121             raw,
122             _marker: marker::PhantomData,
123         }
124     }
raw(&self) -> *mut raw::git_tag125     fn raw(&self) -> *mut raw::git_tag {
126         self.raw
127     }
128 }
129 
130 impl<'repo> Clone for Tag<'repo> {
clone(&self) -> Self131     fn clone(&self) -> Self {
132         self.as_object().clone().into_tag().ok().unwrap()
133     }
134 }
135 
136 impl<'repo> Drop for Tag<'repo> {
drop(&mut self)137     fn drop(&mut self) {
138         unsafe { raw::git_tag_free(self.raw) }
139     }
140 }
141 
142 #[cfg(test)]
143 mod tests {
144     #[test]
smoke()145     fn smoke() {
146         let (_td, repo) = crate::test::repo_init();
147         let head = repo.head().unwrap();
148         let id = head.target().unwrap();
149         assert!(repo.find_tag(id).is_err());
150 
151         let obj = repo.find_object(id, None).unwrap();
152         let sig = repo.signature().unwrap();
153         let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap();
154         let tag = repo.find_tag(tag_id).unwrap();
155         assert_eq!(tag.id(), tag_id);
156 
157         let tags = repo.tag_names(None).unwrap();
158         assert_eq!(tags.len(), 1);
159         assert_eq!(tags.get(0), Some("foo"));
160 
161         assert_eq!(tag.name(), Some("foo"));
162         assert_eq!(tag.message(), Some("msg"));
163         assert_eq!(tag.peel().unwrap().id(), obj.id());
164         assert_eq!(tag.target_id(), obj.id());
165         assert_eq!(tag.target_type(), Some(crate::ObjectType::Commit));
166 
167         assert_eq!(tag.tagger().unwrap().name(), sig.name());
168         tag.target().unwrap();
169         tag.into_object();
170 
171         repo.find_object(tag_id, None).unwrap().as_tag().unwrap();
172         repo.find_object(tag_id, None)
173             .unwrap()
174             .into_tag()
175             .ok()
176             .unwrap();
177 
178         repo.tag_delete("foo").unwrap();
179     }
180 
181     #[test]
lite()182     fn lite() {
183         let (_td, repo) = crate::test::repo_init();
184         let head = t!(repo.head());
185         let id = head.target().unwrap();
186         let obj = t!(repo.find_object(id, None));
187         let tag_id = t!(repo.tag_lightweight("foo", &obj, false));
188         assert!(repo.find_tag(tag_id).is_err());
189         assert_eq!(t!(repo.refname_to_id("refs/tags/foo")), id);
190 
191         let tags = t!(repo.tag_names(Some("f*")));
192         assert_eq!(tags.len(), 1);
193         let tags = t!(repo.tag_names(Some("b*")));
194         assert_eq!(tags.len(), 0);
195     }
196 }
197