use std::cmp::Ordering;
use std::ffi::CString;
use std::marker;
use std::mem;
use std::ptr;
use std::str;
use crate::object::CastOrPanic;
use crate::util::{c_cmp_to_ordering, Binding};
use crate::{
raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceType, Repository, Tag, Tree,
};
struct Refdb<'repo>(&'repo Repository);
pub struct Reference<'repo> {
raw: *mut raw::git_reference,
_marker: marker::PhantomData<Refdb<'repo>>,
}
pub struct References<'repo> {
raw: *mut raw::git_reference_iterator,
_marker: marker::PhantomData<Refdb<'repo>>,
}
pub struct ReferenceNames<'repo, 'references> {
inner: &'references mut References<'repo>,
}
impl<'repo> Reference<'repo> {
pub fn is_valid_name(refname: &str) -> bool {
crate::init();
let refname = CString::new(refname).unwrap();
unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 }
}
pub fn raw(&self) -> *mut raw::git_reference {
self.raw
}
pub fn delete(&mut self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_reference_delete(self.raw));
}
Ok(())
}
pub fn is_branch(&self) -> bool {
unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
}
pub fn is_note(&self) -> bool {
unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
}
pub fn is_remote(&self) -> bool {
unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
}
pub fn is_tag(&self) -> bool {
unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
}
pub fn kind(&self) -> Option<ReferenceType> {
ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
}
pub fn name(&self) -> Option<&str> {
str::from_utf8(self.name_bytes()).ok()
}
pub fn name_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
}
pub fn shorthand(&self) -> Option<&str> {
str::from_utf8(self.shorthand_bytes()).ok()
}
pub fn shorthand_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
}
pub fn target(&self) -> Option<Oid> {
unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
}
pub fn target_peel(&self) -> Option<Oid> {
unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
}
pub fn symbolic_target(&self) -> Option<&str> {
self.symbolic_target_bytes()
.and_then(|s| str::from_utf8(s).ok())
}
pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
}
pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
let mut raw = ptr::null_mut();
unsafe {
try_call!(raw::git_reference_resolve(&mut raw, &*self.raw));
Ok(Binding::from_raw(raw))
}
}
pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
let mut raw = ptr::null_mut();
unsafe {
try_call!(raw::git_reference_peel(&mut raw, self.raw, kind));
Ok(Binding::from_raw(raw))
}
}
pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
}
pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
Ok(self
.peel(ObjectType::Commit)?
.cast_or_panic(ObjectType::Commit))
}
pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
}
pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
}
pub fn rename(
&mut self,
new_name: &str,
force: bool,
msg: &str,
) -> Result<Reference<'repo>, Error> {
let mut raw = ptr::null_mut();
let new_name = CString::new(new_name)?;
let msg = CString::new(msg)?;
unsafe {
try_call!(raw::git_reference_rename(
&mut raw, self.raw, new_name, force, msg
));
Ok(Binding::from_raw(raw))
}
}
pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> {
let mut raw = ptr::null_mut();
let msg = CString::new(reflog_msg)?;
unsafe {
try_call!(raw::git_reference_set_target(
&mut raw,
self.raw,
id.raw(),
msg
));
Ok(Binding::from_raw(raw))
}
}
}
impl<'repo> PartialOrd for Reference<'repo> {
fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'repo> Ord for Reference<'repo> {
fn cmp(&self, other: &Reference<'repo>) -> Ordering {
c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) })
}
}
impl<'repo> PartialEq for Reference<'repo> {
fn eq(&self, other: &Reference<'repo>) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl<'repo> Eq for Reference<'repo> {}
impl<'repo> Binding for Reference<'repo> {
type Raw = *mut raw::git_reference;
unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
Reference {
raw: raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_reference {
self.raw
}
}
impl<'repo> Drop for Reference<'repo> {
fn drop(&mut self) {
unsafe { raw::git_reference_free(self.raw) }
}
}
impl<'repo> References<'repo> {
pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
ReferenceNames { inner: self }
}
}
impl<'repo> Binding for References<'repo> {
type Raw = *mut raw::git_reference_iterator;
unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
References {
raw: raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_reference_iterator {
self.raw
}
}
impl<'repo> Iterator for References<'repo> {
type Item = Result<Reference<'repo>, Error>;
fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
let mut out = ptr::null_mut();
unsafe {
try_call_iter!(raw::git_reference_next(&mut out, self.raw));
Some(Ok(Binding::from_raw(out)))
}
}
}
impl<'repo> Drop for References<'repo> {
fn drop(&mut self) {
unsafe { raw::git_reference_iterator_free(self.raw) }
}
}
impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
type Item = Result<&'references str, Error>;
fn next(&mut self) -> Option<Result<&'references str, Error>> {
let mut out = ptr::null();
unsafe {
try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
let bytes = crate::opt_bytes(self, out).unwrap();
let s = str::from_utf8(bytes).unwrap();
Some(Ok(mem::transmute::<&str, &'references str>(s)))
}
}
}
#[cfg(test)]
mod tests {
use crate::{ObjectType, Reference, ReferenceType};
#[test]
fn smoke() {
assert!(Reference::is_valid_name("refs/foo"));
assert!(!Reference::is_valid_name("foo"));
}
#[test]
fn smoke2() {
let (_td, repo) = crate::test::repo_init();
let mut head = repo.head().unwrap();
assert!(head.is_branch());
assert!(!head.is_remote());
assert!(!head.is_tag());
assert!(!head.is_note());
assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
assert!(head == repo.head().unwrap());
assert_eq!(head.name(), Some("refs/heads/master"));
assert!(head == repo.find_reference("refs/heads/master").unwrap());
assert_eq!(
repo.refname_to_id("refs/heads/master").unwrap(),
head.target().unwrap()
);
assert!(head.symbolic_target().is_none());
assert!(head.target_peel().is_none());
assert_eq!(head.shorthand(), Some("master"));
assert!(head.resolve().unwrap() == head);
let mut tag1 = repo
.reference("refs/tags/tag1", head.target().unwrap(), false, "test")
.unwrap();
assert!(tag1.is_tag());
assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
assert_eq!(tag1.target().unwrap(), peeled_commit.id());
tag1.delete().unwrap();
let mut sym1 = repo
.reference_symbolic("refs/tags/tag1", "refs/heads/master", false, "test")
.unwrap();
assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
sym1.delete().unwrap();
{
assert!(repo.references().unwrap().count() == 1);
assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
let mut names = repo.references().unwrap();
let mut names = names.names();
assert_eq!(names.next().unwrap().unwrap(), "refs/heads/master");
assert!(names.next().is_none());
assert!(repo.references_glob("foo").unwrap().count() == 0);
assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
}
let mut head = head.rename("refs/foo", true, "test").unwrap();
head.delete().unwrap();
}
}