From d1f590164dcf40dc05724e9702e6d0382d0ed2a1 Mon Sep 17 00:00:00 2001 From: William Throwe Date: Wed, 4 Nov 2015 21:58:47 -0500 Subject: [PATCH] Add bindings for git_treebuilder functions --- libgit2-sys/Cargo.toml | 1 + libgit2-sys/lib.rs | 29 ++++++ src/lib.rs | 2 + src/tree.rs | 2 +- src/treebuilder.rs | 223 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/treebuilder.rs diff --git a/libgit2-sys/Cargo.toml b/libgit2-sys/Cargo.toml index 92ae58a5e2..60eac515aa 100644 --- a/libgit2-sys/Cargo.toml +++ b/libgit2-sys/Cargo.toml @@ -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" diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index a106c723c9..1cc7b47dfc 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -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}; @@ -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 {} @@ -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 { @@ -610,6 +614,7 @@ pub enum git_filemode_t { GIT_FILEMODE_LINK = 0o120000, GIT_FILEMODE_COMMIT = 0o160000, } +} pub use git_filemode_t::*; #[repr(C)] @@ -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)] @@ -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; diff --git a/src/lib.rs b/src/lib.rs index a04b3e62f0..80a7ee9a31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -391,6 +392,7 @@ mod submodule; mod tag; mod time; mod tree; +mod treebuilder; fn init() { static INIT: Once = ONCE_INIT; diff --git a/src/tree.rs b/src/tree.rs index bbafd6fb10..15800a1dfc 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -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, diff --git a/src/treebuilder.rs b/src/treebuilder.rs new file mode 100644 index 0000000000..e542c837df --- /dev/null +++ b/src/treebuilder.rs @@ -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 { + 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, 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>(&self, filename: P) -> Result { + 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>(&mut self, filename: P, oid: Oid, + filemode: i32) -> Result { + 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>(&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(&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 { + 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); + } +}