Skip to content

Commit

Permalink
Add bindings for git_treebuilder functions
Browse files Browse the repository at this point in the history
  • Loading branch information
wthrowe committed Nov 5, 2015
1 parent 6892584 commit d1f5901
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 1 deletion.
1 change: 1 addition & 0 deletions libgit2-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ path = "lib.rs"
libssh2-sys = { version = "0.1.28", optional = true }
libc = "0.2"
libz-sys = "0.1.0"
enum_primitive = "0.1.0"

[build-dependencies]
pkg-config = "0.3"
Expand Down
29 changes: 29 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ extern crate libssh2_sys as libssh2;
#[cfg(all(unix, not(target_os = "macos"), feature = "https"))]
extern crate openssl_sys as openssl;
extern crate libz_sys as libz;
#[macro_use] extern crate enum_primitive;
pub use enum_primitive::FromPrimitive;

use libc::{c_int, c_char, c_uint, size_t, c_uchar, c_void};

Expand Down Expand Up @@ -42,6 +44,7 @@ pub enum git_submodule {}
pub enum git_tag {}
pub enum git_tree {}
pub enum git_tree_entry {}
pub enum git_treebuilder {}
pub enum git_push {}
pub enum git_note {}
pub enum git_note_iterator {}
Expand Down Expand Up @@ -600,6 +603,7 @@ pub enum git_ref_t {
}
pub use git_ref_t::*;

enum_from_primitive!{
#[repr(C)]
#[derive(Copy, Clone)]
pub enum git_filemode_t {
Expand All @@ -610,6 +614,7 @@ pub enum git_filemode_t {
GIT_FILEMODE_LINK = 0o120000,
GIT_FILEMODE_COMMIT = 0o160000,
}
}
pub use git_filemode_t::*;

#[repr(C)]
Expand All @@ -622,6 +627,8 @@ pub use git_treewalk_mode::*;

pub type git_treewalk_cb = extern fn(*const c_char, *const git_tree_entry,
*mut c_void) -> c_int;
pub type git_treebuilder_filter_cb = extern fn(*const git_tree_entry,
*mut c_void) -> c_int;

#[repr(C)]
#[derive(Copy, Clone)]
Expand Down Expand Up @@ -1749,6 +1756,28 @@ extern {
callback: git_treewalk_cb,
payload: *mut c_void) -> c_int;

// treebuilder
pub fn git_treebuilder_new(out: *mut *mut git_treebuilder,
repo: *mut git_repository,
source: *const git_tree) -> c_int;
pub fn git_treebuilder_clear(bld: *mut git_treebuilder);
pub fn git_treebuilder_entrycount(bld: *mut git_treebuilder) -> c_uint;
pub fn git_treebuilder_free(bld: *mut git_treebuilder);
pub fn git_treebuilder_get(bld: *mut git_treebuilder,
filename: *const c_char) -> *const git_tree_entry;
pub fn git_treebuilder_insert(out: *mut *const git_tree_entry,
bld: *mut git_treebuilder,
filename: *const c_char,
id: *const git_oid,
filemode: git_filemode_t) -> c_int;
pub fn git_treebuilder_remove(bld: *mut git_treebuilder,
filename: *const c_char) -> c_int;
pub fn git_treebuilder_filter(bld: *mut git_treebuilder,
filter: git_treebuilder_filter_cb,
payload: *mut c_void);
pub fn git_treebuilder_write(id: *mut git_oid,
bld: *mut git_treebuilder) -> c_int;

// buf
pub fn git_buf_free(buffer: *mut git_buf);
pub fn git_buf_grow(buffer: *mut git_buf, target_size: size_t) -> c_int;
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ pub use submodule::Submodule;
pub use tag::Tag;
pub use time::{Time, IndexTime};
pub use tree::{Tree, TreeEntry, TreeIter};
pub use treebuilder::{TreeBuilder, FilterResult};
pub use util::IntoCString;

/// An enumeration of possible errors that can happen when working with a git
Expand Down Expand Up @@ -391,6 +392,7 @@ mod submodule;
mod tag;
mod time;
mod tree;
mod treebuilder;

fn init() {
static INIT: Once = ONCE_INIT;
Expand Down
2 changes: 1 addition & 1 deletion src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl<'tree> TreeEntry<'tree> {
///
/// The lifetime of the entry is tied to the tree provided and the function
/// is unsafe because the validity of the pointer cannot be guaranteed.
unsafe fn from_raw_const(raw: *const raw::git_tree_entry)
pub unsafe fn from_raw_const(raw: *const raw::git_tree_entry)
-> TreeEntry<'tree> {
TreeEntry {
raw: raw as *mut raw::git_tree_entry,
Expand Down
223 changes: 223 additions & 0 deletions src/treebuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
use libc::{c_int, c_void};
use std::marker;
use std::ffi::OsStr;

use {panic, raw, Error, Oid, Repository, Tree, TreeEntry};
use util::{Binding, IntoCString};

/// Constructor for in-memory trees
pub struct TreeBuilder<'repo> {
raw: *mut raw::git_treebuilder,
_marker: marker::PhantomData<&'repo Repository>,
}

impl<'repo> TreeBuilder<'repo> {
/// Create a new TreeBuilder with no entries
pub fn new(repo: &'repo Repository) -> Result<TreeBuilder, Error> {
unsafe {
let mut ret = 0 as *mut raw::git_treebuilder;
try_call!(raw::git_treebuilder_new(&mut ret, repo.raw(),
0 as *mut raw::git_tree));
Ok(Binding::from_raw(ret))
}
}

/// Create a new TreeBuilder initialized with the entries of the given Tree
pub fn from_tree(repo: &'repo Repository, tree: &Tree<'repo>
) -> Result<TreeBuilder<'repo>, Error> {
unsafe {
let mut ret = 0 as *mut raw::git_treebuilder;
try_call!(raw::git_treebuilder_new(&mut ret, repo.raw(),
tree.raw()));
Ok(Binding::from_raw(ret))
}
}

/// Clear all the entries in the builder
pub fn clear(&mut self) {
unsafe { raw::git_treebuilder_clear(self.raw) }
}

/// Get the number of entries
pub fn len(&self) -> usize {
unsafe { raw::git_treebuilder_entrycount(self.raw) as usize }
}

/// Get en entry from the builder from its filename
pub fn get<P: AsRef<OsStr>>(&self, filename: P) -> Result<TreeEntry, Error> {
let filename = try!(filename.as_ref().into_c_string());
unsafe {
let ret = raw::git_treebuilder_get(self.raw, ::call::convert(&filename));
Ok(TreeEntry::from_raw_const(ret))
}
}

/// Add or update an entry in the builder
///
/// No attempt is made to ensure that the provided Oid points to
/// an object of a reasonable type (or any object at all)
pub fn insert<P: AsRef<OsStr>>(&mut self, filename: P, oid: Oid,
filemode: i32) -> Result<TreeEntry, Error> {
use raw::FromPrimitive;
let filename = try!(filename.as_ref().into_c_string());
let filemode = raw::git_filemode_t::from_i32(filemode)
.expect("Invalid filemode passed to TreeBuilder::insert");

let mut ret = 0 as *const raw::git_tree_entry;
unsafe {
try_call!(raw::git_treebuilder_insert(&mut ret, self.raw, filename,
oid.raw(), filemode));
Ok(TreeEntry::from_raw_const(ret))
}
}

/// Remove an entry from the builder by its filename
pub fn remove<P: AsRef<OsStr>>(&mut self, filename: P) -> Result<(), Error> {
let filename = try!(filename.as_ref().into_c_string());
unsafe {
try_call!(raw::git_treebuilder_remove(self.raw, filename));
}
Ok(())
}

/// Selectively remove entries from the tree
pub fn filter<F>(&mut self, mut filter: F)
where F: FnMut(&TreeEntry) -> FilterResult {
let mut cb: &mut FilterCb = &mut filter;
let ptr = &mut cb as *mut _;
unsafe {
raw::git_treebuilder_filter(self.raw, filter_cb, ptr as *mut _);
panic::check();
}
}

/// Write the contents of the TreeBuilder as a Tree object and
/// return its Oid
pub fn write(&self) -> Result<Oid, Error> {
let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ] };
unsafe {
try_call!(raw::git_treebuilder_write(&mut raw, self.raw()));
Ok(Binding::from_raw(&raw as *const _))
}
}
}

type FilterCb<'a> = FnMut(&TreeEntry) -> FilterResult + 'a;

/// Action to be taken for an entry in TreeBuilder::filter
// We use an enum here rather than a bool because libgit2 and libstd's
// filtering functions disagree on the meaning of the return value, so
// either choice would be confusing.
pub enum FilterResult {
/// The TreeEntry should be kept in the builder
Keep,
/// The TreeEntry should be removed from the builder
Discard,
}

wrap_env! {
fn filter_cb(entry: *const raw::git_tree_entry,
payload: *mut c_void) -> c_int {
unsafe {
// There's no way to return early from git_treebuilder_filter, so
// just keep panicing until it stops calling us.
panic::check();
let entry = TreeEntry::from_raw_const(entry);
let payload = payload as *mut &mut FilterCb;
(*payload)(&entry)
}
}
returning ret as match ret {
Some(FilterResult::Keep) => 0,
Some(FilterResult::Discard) => 1,
None => 0,
}
}

impl<'repo> Binding for TreeBuilder<'repo> {
type Raw = *mut raw::git_treebuilder;

unsafe fn from_raw(raw: *mut raw::git_treebuilder) -> TreeBuilder<'repo> {
TreeBuilder { raw: raw, _marker: marker::PhantomData }
}
fn raw(&self) -> *mut raw::git_treebuilder { self.raw }
}

impl<'repo> Drop for TreeBuilder<'repo> {
fn drop(&mut self) {
unsafe { raw::git_treebuilder_free(self.raw) }
}
}

#[cfg(test)]
mod tests {
use {TreeBuilder, FilterResult};
use ObjectType;

#[test]
fn smoke() {
let (_td, repo) = ::test::repo_init();

let mut builder = TreeBuilder::new(&repo).unwrap();
assert_eq!(builder.len(), 0);
let blob = repo.blob(b"data").unwrap();
{
let entry = builder.insert("a", blob, 0o100644).unwrap();
assert_eq!(entry.kind(), Some(ObjectType::Blob));
}
builder.insert("b", blob, 0o100644).unwrap();
assert_eq!(builder.len(), 2);
builder.remove("a").unwrap();
assert_eq!(builder.len(), 1);
assert_eq!(builder.get("b").unwrap().id(), blob);
builder.clear();
assert_eq!(builder.len(), 0);
}

#[test]
fn write() {
let (_td, repo) = ::test::repo_init();

let mut builder = TreeBuilder::new(&repo).unwrap();
let data = repo.blob(b"data").unwrap();
builder.insert("name", data, 0o100644).unwrap();
let tree = builder.write().unwrap();
let tree = repo.find_tree(tree).unwrap();
let entry = tree.get(0).unwrap();
assert_eq!(entry.name(), Some("name"));
let blob = entry.to_object(&repo).unwrap();
let blob = blob.as_blob().unwrap();
assert_eq!(blob.content(), b"data");

let builder = TreeBuilder::from_tree(&repo, &tree).unwrap();
assert_eq!(builder.len(), 1);
}

#[test]
fn filter() {
let (_td, repo) = ::test::repo_init();

let mut builder = TreeBuilder::new(&repo).unwrap();
let blob = repo.blob(b"data").unwrap();
let tree = {
let head = repo.head().unwrap()
.peel(ObjectType::Commit).unwrap();
let head = head.as_commit().unwrap();
head.tree_id()
};
builder.insert("blob", blob, 0o100644).unwrap();
builder.insert("dir", tree, 0o040000).unwrap();
builder.insert("dir2", tree, 0o040000).unwrap();

builder.filter(|_| FilterResult::Keep);
assert_eq!(builder.len(), 3);
builder.filter(|e| if e.kind().unwrap() == ObjectType::Blob {
FilterResult::Discard
} else {
FilterResult::Keep
});
assert_eq!(builder.len(), 2);
builder.filter(|_| FilterResult::Discard);
assert_eq!(builder.len(), 0);
}
}

0 comments on commit d1f5901

Please sign in to comment.