use std::cmp::Ordering;
use std::ffi::CString;
use std::kinds::marker;
use std::mem;
use std::str;
use libc;
use {raw, Error, Oid, Signature};
pub struct Reference<'repo> {
raw: *mut raw::git_reference,
marker: marker::ContravariantLifetime<'repo>,
}
pub struct References<'repo> {
raw: *mut raw::git_reference_iterator,
marker: marker::ContravariantLifetime<'repo>,
}
pub struct ReferenceNames<'repo> {
inner: References<'repo>,
}
impl<'repo> Reference<'repo> {
pub unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
Reference {
raw: raw,
marker: marker::ContravariantLifetime,
}
}
pub fn is_valid_name(refname: &str) -> bool {
::init();
let refname = CString::from_slice(refname.as_bytes());
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 name(&self) -> Option<&str> { str::from_utf8(self.name_bytes()).ok() }
pub fn name_bytes(&self) -> &[u8] {
unsafe { ::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 {
::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap()
}
}
pub fn target(&self) -> Option<Oid> {
let ptr = unsafe { raw::git_reference_target(&*self.raw) };
if ptr.is_null() {None} else {Some(unsafe { Oid::from_raw(ptr) })}
}
pub fn target_peel(&self) -> Option<Oid> {
let ptr = unsafe { raw::git_reference_target_peel(&*self.raw) };
if ptr.is_null() {None} else {Some(unsafe { Oid::from_raw(ptr) })}
}
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 { ::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
}
pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
let mut raw = 0 as *mut raw::git_reference;
unsafe { try_call!(raw::git_reference_resolve(&mut raw, &*self.raw)); }
Ok(Reference {
raw: raw,
marker: marker::ContravariantLifetime,
})
}
pub fn rename(&mut self, new_name: &str, force: bool,
sig: Option<&Signature>,
msg: &str) -> Result<Reference<'repo>, Error> {
let mut raw = 0 as *mut raw::git_reference;
let new_name = CString::from_slice(new_name.as_bytes());
let msg = CString::from_slice(msg.as_bytes());
unsafe {
try_call!(raw::git_reference_rename(&mut raw, self.raw,
new_name,
force,
&*sig.map(|s| s.raw())
.unwrap_or(0 as *mut _),
msg));
}
Ok(Reference {
raw: raw,
marker: marker::ContravariantLifetime,
})
}
}
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 {
match unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) } {
0 => Ordering::Equal,
n if n < 0 => Ordering::Less,
_ => Ordering::Greater,
}
}
}
impl<'repo> PartialEq for Reference<'repo> {
fn eq(&self, other: &Reference<'repo>) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl<'repo> Eq for Reference<'repo> {}
#[unsafe_destructor]
impl<'repo> Drop for Reference<'repo> {
fn drop(&mut self) {
unsafe { raw::git_reference_free(self.raw) }
}
}
impl<'repo> References<'repo> {
pub unsafe fn from_raw(raw: *mut raw::git_reference_iterator)
-> References<'repo> {
References {
raw: raw,
marker: marker::ContravariantLifetime,
}
}
}
impl<'repo> Iterator for References<'repo> {
type Item = Reference<'repo>;
fn next(&mut self) -> Option<Reference<'repo>> {
let mut out = 0 as *mut raw::git_reference;
if unsafe { raw::git_reference_next(&mut out, self.raw) == 0 } {
Some(unsafe { Reference::from_raw(out) })
} else {
None
}
}
}
#[unsafe_destructor]
impl<'repo> Drop for References<'repo> {
fn drop(&mut self) {
unsafe { raw::git_reference_iterator_free(self.raw) }
}
}
impl<'repo> ReferenceNames<'repo> {
pub fn new(refs: References) -> ReferenceNames {
ReferenceNames { inner: refs }
}
}
impl<'repo> Iterator for ReferenceNames<'repo> {
type Item = &'repo str;
fn next(&mut self) -> Option<&'repo str> {
let mut out = 0 as *const libc::c_char;
if unsafe { raw::git_reference_next_name(&mut out, self.inner.raw) == 0 } {
Some(unsafe {
let bytes = ::opt_bytes(self, out).unwrap();
let s = str::from_utf8(bytes).unwrap();
mem::transmute::<&str, &'repo str>(s)
})
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use {Reference};
#[test]
fn smoke() {
assert!(Reference::is_valid_name("refs/foo"));
assert!(!Reference::is_valid_name("foo"));
}
#[test]
fn smoke2() {
let (_td, repo) = ::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!(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 sig = repo.signature().unwrap();
let mut tag1 = repo.reference("refs/tags/tag1",
head.target().unwrap(),
false,
None, "test").unwrap();
assert!(tag1.is_tag());
tag1.delete().unwrap();
let mut sym1 = repo.reference_symbolic("refs/tags/tag1",
"refs/heads/master", false,
Some(&sig), "test").unwrap();
sym1.delete().unwrap();
{
assert!(repo.references().unwrap().count() == 1);
assert!(repo.references().unwrap().next().unwrap() == head);
let mut names = ::ReferenceNames::new(repo.references().unwrap());
assert_eq!(names.next(), Some("refs/heads/master"));
assert_eq!(names.next(), 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, None, "test").unwrap();
head.delete().unwrap();
}
}