use std::cmp::Ordering;
use std::ffi::CString;
use std::io;
use std::kinds::marker;
use std::str;
use libc;
use {raw, Oid, Repository, Error, Object, ObjectType};
pub struct Tree<'repo> {
raw: *mut raw::git_tree,
marker: marker::ContravariantLifetime<'repo>,
}
pub struct TreeEntry<'tree> {
raw: *mut raw::git_tree_entry,
owned: bool,
marker: marker::ContravariantLifetime<'tree>,
}
impl<'repo> Tree<'repo> {
pub unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> {
Tree {
raw: raw,
marker: marker::ContravariantLifetime,
}
}
pub fn id(&self) -> Oid {
unsafe { Oid::from_raw(raw::git_tree_id(&*self.raw)) }
}
pub fn raw(&self) -> *mut raw::git_tree { self.raw }
pub fn len(&self) -> uint {
unsafe { raw::git_tree_entrycount(&*self.raw) as uint }
}
pub fn get_id(&self, id: Oid) -> Option<TreeEntry> {
unsafe {
let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw());
if ptr.is_null() {
None
} else {
Some(TreeEntry::from_raw_const(ptr))
}
}
}
pub fn get(&self, n: uint) -> Option<TreeEntry> {
unsafe {
let ptr = raw::git_tree_entry_byindex(&*self.raw(),
n as libc::size_t);
if ptr.is_null() {
None
} else {
Some(TreeEntry::from_raw_const(ptr))
}
}
}
pub fn get_name(&self, filename: &str) -> Option<TreeEntry> {
let filename = CString::from_slice(filename.as_bytes());
unsafe {
let ptr = call!(raw::git_tree_entry_byname(&*self.raw(),
filename));
if ptr.is_null() {
None
} else {
Some(TreeEntry::from_raw_const(ptr))
}
}
}
pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> {
let mut ret = 0 as *mut raw::git_tree_entry;
let path = CString::from_slice(path.as_vec());
unsafe {
try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path));
Ok(TreeEntry::from_raw(ret))
}
}
}
#[unsafe_destructor]
impl<'repo> Drop for Tree<'repo> {
fn drop(&mut self) {
unsafe { raw::git_tree_free(self.raw) }
}
}
impl<'tree> TreeEntry<'tree> {
pub unsafe fn from_raw_const(raw: *const raw::git_tree_entry)
-> TreeEntry<'tree> {
TreeEntry {
raw: raw as *mut raw::git_tree_entry,
owned: false,
marker: marker::ContravariantLifetime,
}
}
pub unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'static> {
TreeEntry {
raw: raw,
owned: true,
marker: marker::ContravariantLifetime,
}
}
pub fn raw(&self) -> *mut raw::git_tree_entry { self.raw }
pub fn id(&self) -> Oid {
unsafe { Oid::from_raw(raw::git_tree_entry_id(&*self.raw)) }
}
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_tree_entry_name(&*self.raw())).unwrap()
}
}
pub fn to_object<'a>(&self, repo: &'a Repository)
-> Result<Object<'a>, Error> {
let mut ret = 0 as *mut raw::git_object;
unsafe {
try_call!(raw::git_tree_entry_to_object(&mut ret, repo.raw(),
&*self.raw()));
Ok(Object::from_raw(ret))
}
}
pub fn kind(&self) -> Option<ObjectType> {
ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) })
}
pub fn filemode(&self) -> io::FilePermission {
io::FilePermission::from_bits_truncate(unsafe {
raw::git_tree_entry_filemode(&*self.raw) as u32
})
}
pub fn filemode_raw(&self) -> i32 {
unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 }
}
}
impl<'a> Clone for TreeEntry<'a> {
fn clone(&self) -> TreeEntry<'a> {
let mut ret = 0 as *mut raw::git_tree_entry;
unsafe {
assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0);
TreeEntry::from_raw(ret)
}
}
}
impl<'a> PartialOrd for TreeEntry<'a> {
fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for TreeEntry<'a> {
fn cmp(&self, other: &TreeEntry<'a>) -> Ordering {
match unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) } {
0 => Ordering::Equal,
n if n < 0 => Ordering::Less,
_ => Ordering::Greater,
}
}
}
impl<'a> PartialEq for TreeEntry<'a> {
fn eq(&self, other: &TreeEntry<'a>) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl<'a> Eq for TreeEntry<'a> {}
#[unsafe_destructor]
impl<'a> Drop for TreeEntry<'a> {
fn drop(&mut self) {
if self.owned {
unsafe { raw::git_tree_entry_free(self.raw) }
}
}
}
#[cfg(test)]
mod tests {
use std::io::File;
#[test]
fn smoke() {
let (td, repo) = ::test::repo_init();
{
let mut index = repo.index().unwrap();
File::create(&td.path().join("foo")).write_str("foo").unwrap();
index.add_path(&Path::new("foo")).unwrap();
let id = index.write_tree().unwrap();
let sig = repo.signature().unwrap();
let tree = repo.find_tree(id).unwrap();
let parent = repo.find_commit(repo.head().unwrap().target()
.unwrap()).unwrap();
repo.commit(Some("HEAD"), &sig, &sig, "another commit",
&tree, &[&parent]).unwrap();
}
let head = repo.head().unwrap();
let target = head.target().unwrap();
let commit = repo.find_commit(target).unwrap();
let tree = repo.find_tree(commit.tree_id()).unwrap();
assert_eq!(tree.id(), commit.tree_id());
assert_eq!(tree.len(), 1);
let e1 = tree.get(0).unwrap();
assert!(e1 == tree.get_id(e1.id()).unwrap());
assert!(e1 == tree.get_name("foo").unwrap());
assert!(e1 == tree.get_path(&Path::new("foo")).unwrap());
assert_eq!(e1.name(), Some("foo"));
e1.to_object(&repo).unwrap();
}
}