1 use std::marker; 2 use std::mem; 3 use std::ptr; 4 5 use crate::util::Binding; 6 use crate::{raw, Blob, Buf, Commit, Error, ObjectType, Oid, Repository, Tag, Tree}; 7 use crate::{Describe, DescribeOptions}; 8 9 /// A structure to represent a git [object][1] 10 /// 11 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects 12 pub struct Object<'repo> { 13 raw: *mut raw::git_object, 14 _marker: marker::PhantomData<&'repo Repository>, 15 } 16 17 impl<'repo> Object<'repo> { 18 /// Get the id (SHA1) of a repository object id(&self) -> Oid19 pub fn id(&self) -> Oid { 20 unsafe { Binding::from_raw(raw::git_object_id(&*self.raw)) } 21 } 22 23 /// Get the object type of an object. 24 /// 25 /// If the type is unknown, then `None` is returned. kind(&self) -> Option<ObjectType>26 pub fn kind(&self) -> Option<ObjectType> { 27 ObjectType::from_raw(unsafe { raw::git_object_type(&*self.raw) }) 28 } 29 30 /// Recursively peel an object until an object of the specified type is met. 31 /// 32 /// If you pass `Any` as the target type, then the object will be 33 /// peeled until the type changes (e.g. a tag will be chased until the 34 /// referenced object is no longer a tag). peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error>35 pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { 36 let mut raw = ptr::null_mut(); 37 unsafe { 38 try_call!(raw::git_object_peel(&mut raw, &*self.raw(), kind)); 39 Ok(Binding::from_raw(raw)) 40 } 41 } 42 43 /// Recursively peel an object until a blob is found peel_to_blob(&self) -> Result<Blob<'repo>, Error>44 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { 45 self.peel(ObjectType::Blob) 46 .map(|o| o.cast_or_panic(ObjectType::Blob)) 47 } 48 49 /// Recursively peel an object until a commit is found peel_to_commit(&self) -> Result<Commit<'repo>, Error>50 pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { 51 self.peel(ObjectType::Commit) 52 .map(|o| o.cast_or_panic(ObjectType::Commit)) 53 } 54 55 /// Recursively peel an object until a tag is found peel_to_tag(&self) -> Result<Tag<'repo>, Error>56 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { 57 self.peel(ObjectType::Tag) 58 .map(|o| o.cast_or_panic(ObjectType::Tag)) 59 } 60 61 /// Recursively peel an object until a tree is found peel_to_tree(&self) -> Result<Tree<'repo>, Error>62 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { 63 self.peel(ObjectType::Tree) 64 .map(|o| o.cast_or_panic(ObjectType::Tree)) 65 } 66 67 /// Get a short abbreviated OID string for the object 68 /// 69 /// This starts at the "core.abbrev" length (default 7 characters) and 70 /// iteratively extends to a longer string if that length is ambiguous. The 71 /// result will be unambiguous (at least until new objects are added to the 72 /// repository). short_id(&self) -> Result<Buf, Error>73 pub fn short_id(&self) -> Result<Buf, Error> { 74 unsafe { 75 let buf = Buf::new(); 76 try_call!(raw::git_object_short_id(buf.raw(), &*self.raw())); 77 Ok(buf) 78 } 79 } 80 81 /// Attempt to view this object as a commit. 82 /// 83 /// Returns `None` if the object is not actually a commit. as_commit(&self) -> Option<&Commit<'repo>>84 pub fn as_commit(&self) -> Option<&Commit<'repo>> { 85 self.cast(ObjectType::Commit) 86 } 87 88 /// Attempt to consume this object and return a commit. 89 /// 90 /// Returns `Err(self)` if this object is not actually a commit. into_commit(self) -> Result<Commit<'repo>, Object<'repo>>91 pub fn into_commit(self) -> Result<Commit<'repo>, Object<'repo>> { 92 self.cast_into(ObjectType::Commit) 93 } 94 95 /// Attempt to view this object as a tag. 96 /// 97 /// Returns `None` if the object is not actually a tag. as_tag(&self) -> Option<&Tag<'repo>>98 pub fn as_tag(&self) -> Option<&Tag<'repo>> { 99 self.cast(ObjectType::Tag) 100 } 101 102 /// Attempt to consume this object and return a tag. 103 /// 104 /// Returns `Err(self)` if this object is not actually a tag. into_tag(self) -> Result<Tag<'repo>, Object<'repo>>105 pub fn into_tag(self) -> Result<Tag<'repo>, Object<'repo>> { 106 self.cast_into(ObjectType::Tag) 107 } 108 109 /// Attempt to view this object as a tree. 110 /// 111 /// Returns `None` if the object is not actually a tree. as_tree(&self) -> Option<&Tree<'repo>>112 pub fn as_tree(&self) -> Option<&Tree<'repo>> { 113 self.cast(ObjectType::Tree) 114 } 115 116 /// Attempt to consume this object and return a tree. 117 /// 118 /// Returns `Err(self)` if this object is not actually a tree. into_tree(self) -> Result<Tree<'repo>, Object<'repo>>119 pub fn into_tree(self) -> Result<Tree<'repo>, Object<'repo>> { 120 self.cast_into(ObjectType::Tree) 121 } 122 123 /// Attempt to view this object as a blob. 124 /// 125 /// Returns `None` if the object is not actually a blob. as_blob(&self) -> Option<&Blob<'repo>>126 pub fn as_blob(&self) -> Option<&Blob<'repo>> { 127 self.cast(ObjectType::Blob) 128 } 129 130 /// Attempt to consume this object and return a blob. 131 /// 132 /// Returns `Err(self)` if this object is not actually a blob. into_blob(self) -> Result<Blob<'repo>, Object<'repo>>133 pub fn into_blob(self) -> Result<Blob<'repo>, Object<'repo>> { 134 self.cast_into(ObjectType::Blob) 135 } 136 137 /// Describes a commit 138 /// 139 /// Performs a describe operation on this commitish object. describe(&self, opts: &DescribeOptions) -> Result<Describe<'_>, Error>140 pub fn describe(&self, opts: &DescribeOptions) -> Result<Describe<'_>, Error> { 141 let mut ret = ptr::null_mut(); 142 unsafe { 143 try_call!(raw::git_describe_commit(&mut ret, self.raw, opts.raw())); 144 Ok(Binding::from_raw(ret)) 145 } 146 } 147 cast<T>(&self, kind: ObjectType) -> Option<&T>148 fn cast<T>(&self, kind: ObjectType) -> Option<&T> { 149 assert_eq!(mem::size_of::<Object<'_>>(), mem::size_of::<T>()); 150 if self.kind() == Some(kind) { 151 unsafe { Some(&*(self as *const _ as *const T)) } 152 } else { 153 None 154 } 155 } 156 cast_into<T>(self, kind: ObjectType) -> Result<T, Object<'repo>>157 fn cast_into<T>(self, kind: ObjectType) -> Result<T, Object<'repo>> { 158 assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); 159 if self.kind() == Some(kind) { 160 Ok(unsafe { 161 let other = ptr::read(&self as *const _ as *const T); 162 mem::forget(self); 163 other 164 }) 165 } else { 166 Err(self) 167 } 168 } 169 } 170 171 /// This trait is useful to export cast_or_panic into crate but not outside 172 pub trait CastOrPanic { cast_or_panic<T>(self, kind: ObjectType) -> T173 fn cast_or_panic<T>(self, kind: ObjectType) -> T; 174 } 175 176 impl<'repo> CastOrPanic for Object<'repo> { cast_or_panic<T>(self, kind: ObjectType) -> T177 fn cast_or_panic<T>(self, kind: ObjectType) -> T { 178 assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); 179 if self.kind() == Some(kind) { 180 unsafe { 181 let other = ptr::read(&self as *const _ as *const T); 182 mem::forget(self); 183 other 184 } 185 } else { 186 let buf; 187 let akind = match self.kind() { 188 Some(akind) => akind.str(), 189 None => { 190 buf = format!("unknown ({})", unsafe { raw::git_object_type(&*self.raw) }); 191 &buf 192 } 193 }; 194 panic!( 195 "Expected object {} to be {} but it is {}", 196 self.id(), 197 kind.str(), 198 akind 199 ) 200 } 201 } 202 } 203 204 impl<'repo> Clone for Object<'repo> { clone(&self) -> Object<'repo>205 fn clone(&self) -> Object<'repo> { 206 let mut raw = ptr::null_mut(); 207 unsafe { 208 let rc = raw::git_object_dup(&mut raw, self.raw); 209 assert_eq!(rc, 0); 210 Binding::from_raw(raw) 211 } 212 } 213 } 214 215 impl<'repo> std::fmt::Debug for Object<'repo> { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 217 let mut ds = f.debug_struct("Object"); 218 match self.kind() { 219 Some(kind) => ds.field("kind", &kind), 220 None => ds.field( 221 "kind", 222 &format!("Unknow ({})", unsafe { raw::git_object_type(&*self.raw) }), 223 ), 224 }; 225 ds.field("id", &self.id()); 226 ds.finish() 227 } 228 } 229 230 impl<'repo> Binding for Object<'repo> { 231 type Raw = *mut raw::git_object; 232 from_raw(raw: *mut raw::git_object) -> Object<'repo>233 unsafe fn from_raw(raw: *mut raw::git_object) -> Object<'repo> { 234 Object { 235 raw: raw, 236 _marker: marker::PhantomData, 237 } 238 } raw(&self) -> *mut raw::git_object239 fn raw(&self) -> *mut raw::git_object { 240 self.raw 241 } 242 } 243 244 impl<'repo> Drop for Object<'repo> { drop(&mut self)245 fn drop(&mut self) { 246 unsafe { raw::git_object_free(self.raw) } 247 } 248 } 249