diff options
Diffstat (limited to 'vendor/git2/src/object.rs')
-rw-r--r-- | vendor/git2/src/object.rs | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/vendor/git2/src/object.rs b/vendor/git2/src/object.rs new file mode 100644 index 0000000..fcae006 --- /dev/null +++ b/vendor/git2/src/object.rs @@ -0,0 +1,248 @@ +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> { + 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<Object<'repo>, 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<Blob<'repo>, 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<Commit<'repo>, 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<Tag<'repo>, 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<Tree<'repo>, 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<Buf, Error> { + 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<Commit<'repo>, 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<Tag<'repo>, 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<Tree<'repo>, 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<Blob<'repo>, 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<Describe<'_>, 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<T>(&self, kind: ObjectType) -> Option<&T> { + assert_eq!(mem::size_of::<Object<'_>>(), mem::size_of::<T>()); + if self.kind() == Some(kind) { + unsafe { Some(&*(self as *const _ as *const T)) } + } else { + None + } + } + + fn cast_into<T>(self, kind: ObjectType) -> Result<T, Object<'repo>> { + assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); + 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<T>(self, kind: ObjectType) -> T; +} + +impl<'repo> CastOrPanic for Object<'repo> { + fn cast_or_panic<T>(self, kind: ObjectType) -> T { + assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); + 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) } + } +} |