use std::marker; use std::mem; use std::ptr; use crate::util::Binding; use crate::{raw, Blob, Buf, Commit, Error, ObjectType, Oid, Repository, Tag, Tree}; use crate::{Describe, DescribeOptions}; /// A structure to represent a git [object][1] /// /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects pub struct Object<'repo> { raw: *mut raw::git_object, _marker: marker::PhantomData<&'repo Repository>, } impl<'repo> Object<'repo> { /// Get the id (SHA1) of a repository object pub fn id(&self) -> Oid { unsafe { Binding::from_raw(raw::git_object_id(&*self.raw)) } } /// Get the object type of an object. /// /// If the type is unknown, then `None` is returned. pub fn kind(&self) -> Option { ObjectType::from_raw(unsafe { raw::git_object_type(&*self.raw) }) } /// Recursively peel an object until an object of the specified type is met. /// /// If you pass `Any` as the target type, then the object will be /// peeled until the type changes (e.g. a tag will be chased until the /// referenced object is no longer a tag). pub fn peel(&self, kind: ObjectType) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_object_peel(&mut raw, &*self.raw(), kind)); Ok(Binding::from_raw(raw)) } } /// Recursively peel an object until a blob is found pub fn peel_to_blob(&self) -> Result, Error> { self.peel(ObjectType::Blob) .map(|o| o.cast_or_panic(ObjectType::Blob)) } /// Recursively peel an object until a commit is found pub fn peel_to_commit(&self) -> Result, Error> { self.peel(ObjectType::Commit) .map(|o| o.cast_or_panic(ObjectType::Commit)) } /// Recursively peel an object until a tag is found pub fn peel_to_tag(&self) -> Result, Error> { self.peel(ObjectType::Tag) .map(|o| o.cast_or_panic(ObjectType::Tag)) } /// Recursively peel an object until a tree is found pub fn peel_to_tree(&self) -> Result, Error> { self.peel(ObjectType::Tree) .map(|o| o.cast_or_panic(ObjectType::Tree)) } /// Get a short abbreviated OID string for the object /// /// This starts at the "core.abbrev" length (default 7 characters) and /// iteratively extends to a longer string if that length is ambiguous. The /// result will be unambiguous (at least until new objects are added to the /// repository). pub fn short_id(&self) -> Result { unsafe { let buf = Buf::new(); try_call!(raw::git_object_short_id(buf.raw(), &*self.raw())); Ok(buf) } } /// Attempt to view this object as a commit. /// /// Returns `None` if the object is not actually a commit. pub fn as_commit(&self) -> Option<&Commit<'repo>> { self.cast(ObjectType::Commit) } /// Attempt to consume this object and return a commit. /// /// Returns `Err(self)` if this object is not actually a commit. pub fn into_commit(self) -> Result, Object<'repo>> { self.cast_into(ObjectType::Commit) } /// Attempt to view this object as a tag. /// /// Returns `None` if the object is not actually a tag. pub fn as_tag(&self) -> Option<&Tag<'repo>> { self.cast(ObjectType::Tag) } /// Attempt to consume this object and return a tag. /// /// Returns `Err(self)` if this object is not actually a tag. pub fn into_tag(self) -> Result, Object<'repo>> { self.cast_into(ObjectType::Tag) } /// Attempt to view this object as a tree. /// /// Returns `None` if the object is not actually a tree. pub fn as_tree(&self) -> Option<&Tree<'repo>> { self.cast(ObjectType::Tree) } /// Attempt to consume this object and return a tree. /// /// Returns `Err(self)` if this object is not actually a tree. pub fn into_tree(self) -> Result, Object<'repo>> { self.cast_into(ObjectType::Tree) } /// Attempt to view this object as a blob. /// /// Returns `None` if the object is not actually a blob. pub fn as_blob(&self) -> Option<&Blob<'repo>> { self.cast(ObjectType::Blob) } /// Attempt to consume this object and return a blob. /// /// Returns `Err(self)` if this object is not actually a blob. pub fn into_blob(self) -> Result, Object<'repo>> { self.cast_into(ObjectType::Blob) } /// Describes a commit /// /// Performs a describe operation on this commitish object. pub fn describe(&self, opts: &DescribeOptions) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_describe_commit(&mut ret, self.raw, opts.raw())); Ok(Binding::from_raw(ret)) } } fn cast(&self, kind: ObjectType) -> Option<&T> { assert_eq!(mem::size_of::>(), mem::size_of::()); if self.kind() == Some(kind) { unsafe { Some(&*(self as *const _ as *const T)) } } else { None } } fn cast_into(self, kind: ObjectType) -> Result> { assert_eq!(mem::size_of_val(&self), mem::size_of::()); if self.kind() == Some(kind) { Ok(unsafe { let other = ptr::read(&self as *const _ as *const T); mem::forget(self); other }) } else { Err(self) } } } /// This trait is useful to export cast_or_panic into crate but not outside pub trait CastOrPanic { fn cast_or_panic(self, kind: ObjectType) -> T; } impl<'repo> CastOrPanic for Object<'repo> { fn cast_or_panic(self, kind: ObjectType) -> T { assert_eq!(mem::size_of_val(&self), mem::size_of::()); if self.kind() == Some(kind) { unsafe { let other = ptr::read(&self as *const _ as *const T); mem::forget(self); other } } else { let buf; let akind = match self.kind() { Some(akind) => akind.str(), None => { buf = format!("unknown ({})", unsafe { raw::git_object_type(&*self.raw) }); &buf } }; panic!( "Expected object {} to be {} but it is {}", self.id(), kind.str(), akind ) } } } impl<'repo> Clone for Object<'repo> { fn clone(&self) -> Object<'repo> { let mut raw = ptr::null_mut(); unsafe { let rc = raw::git_object_dup(&mut raw, self.raw); assert_eq!(rc, 0); Binding::from_raw(raw) } } } impl<'repo> std::fmt::Debug for Object<'repo> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { let mut ds = f.debug_struct("Object"); match self.kind() { Some(kind) => ds.field("kind", &kind), None => ds.field( "kind", &format!("Unknow ({})", unsafe { raw::git_object_type(&*self.raw) }), ), }; ds.field("id", &self.id()); ds.finish() } } impl<'repo> Binding for Object<'repo> { type Raw = *mut raw::git_object; unsafe fn from_raw(raw: *mut raw::git_object) -> Object<'repo> { Object { raw, _marker: marker::PhantomData, } } fn raw(&self) -> *mut raw::git_object { self.raw } } impl<'repo> Drop for Object<'repo> { fn drop(&mut self) { unsafe { raw::git_object_free(self.raw) } } }