git2/
object.rs

1use std::marker;
2use std::mem;
3use std::ptr;
4
5use crate::util::Binding;
6use crate::{raw, Blob, Buf, Commit, Error, ObjectType, Oid, Repository, Tag, Tree};
7use 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
12pub struct Object<'repo> {
13    raw: *mut raw::git_object,
14    _marker: marker::PhantomData<&'repo Repository>,
15}
16
17impl<'repo> Object<'repo> {
18    /// Get the id (SHA1) of a repository object
19    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.
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).
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
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
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
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
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
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
172pub trait CastOrPanic {
173    fn cast_or_panic<T>(self, kind: ObjectType) -> T;
174}
175
176impl<'repo> CastOrPanic for Object<'repo> {
177    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
204impl<'repo> Clone for 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
215impl<'repo> std::fmt::Debug for Object<'repo> {
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
230impl<'repo> Binding for Object<'repo> {
231    type Raw = *mut raw::git_object;
232
233    unsafe fn from_raw(raw: *mut raw::git_object) -> Object<'repo> {
234        Object {
235            raw,
236            _marker: marker::PhantomData,
237        }
238    }
239    fn raw(&self) -> *mut raw::git_object {
240        self.raw
241    }
242}
243
244impl<'repo> Drop for Object<'repo> {
245    fn drop(&mut self) {
246        unsafe { raw::git_object_free(self.raw) }
247    }
248}