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