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