use std::marker;
use std::ptr;
use libc::{c_int, c_void};
use crate::util::{Binding, IntoCString};
use crate::{panic, raw, tree, Error, Oid, Repository, TreeEntry};
/// Constructor for in-memory trees (low-level)
///
/// You probably want to use [`build::TreeUpdateBuilder`] instead.
///
/// This is the more raw of the two tree update facilities. It
/// handles only one level of a nested tree structure at a time. Each
/// path passed to `insert` etc. must be a single component.
///
/// [`build::TreeUpdateBuilder`]: crate::build::TreeUpdateBuilder
pub struct TreeBuilder<'repo> {
raw: *mut raw::git_treebuilder,
_marker: marker::PhantomData<&'repo Repository>,
}
impl<'repo> TreeBuilder<'repo> {
/// Clear all the entries in the builder
pub fn clear(&mut self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_treebuilder_clear(self.raw));
}
Ok(())
}
/// Get the number of entries
pub fn len(&self) -> usize {
unsafe { raw::git_treebuilder_entrycount(self.raw) as usize }
}
/// Return `true` if there is no entry
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Get en entry from the builder from its filename
pub fn get
(&self, filename: P) -> Result>, Error>
where
P: IntoCString,
{
let filename = filename.into_c_string()?;
unsafe {
let ret = raw::git_treebuilder_get(self.raw, filename.as_ptr());
if ret.is_null() {
Ok(None)
} else {
Ok(Some(tree::entry_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).
///
/// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or
/// 0o160000 currently.
pub fn insert(
&mut self,
filename: P,
oid: Oid,
filemode: i32,
) -> Result, Error> {
let filename = filename.into_c_string()?;
let filemode = filemode as raw::git_filemode_t;
let mut ret = ptr::null();
unsafe {
try_call!(raw::git_treebuilder_insert(
&mut ret,
self.raw,
filename,
oid.raw(),
filemode
));
Ok(tree::entry_from_raw_const(ret))
}
}
/// Remove an entry from the builder by its filename
pub fn remove(&mut self, filename: P) -> Result<(), Error> {
let filename = filename.into_c_string()?;
unsafe {
try_call!(raw::git_treebuilder_remove(self.raw, filename));
}
Ok(())
}
/// Selectively remove entries from the tree
///
/// Values for which the filter returns `true` will be kept. Note
/// that this behavior is different from the libgit2 C interface.
pub fn filter(&mut self, mut filter: F) -> Result<(), Error>
where
F: FnMut(&TreeEntry<'_>) -> bool,
{
let mut cb: &mut FilterCb<'_> = &mut filter;
let ptr = &mut cb as *mut _;
let cb: raw::git_treebuilder_filter_cb = Some(filter_cb);
unsafe {
try_call!(raw::git_treebuilder_filter(self.raw, cb, ptr as *mut _));
panic::check();
}
Ok(())
}
/// 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> = dyn FnMut(&TreeEntry<'_>) -> bool + 'a;
extern "C" fn filter_cb(entry: *const raw::git_tree_entry, payload: *mut c_void) -> c_int {
let ret = panic::wrap(|| unsafe {
// There's no way to return early from git_treebuilder_filter.
if panic::panicked() {
true
} else {
let entry = tree::entry_from_raw_const(entry);
let payload = payload as *mut &mut FilterCb<'_>;
(*payload)(&entry)
}
});
if ret == Some(false) {
1
} else {
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,
_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 crate::ObjectType;
#[test]
fn smoke() {
let (_td, repo) = crate::test::repo_init();
let mut builder = repo.treebuilder(None).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().unwrap().id(), blob);
builder.clear().unwrap();
assert_eq!(builder.len(), 0);
}
#[test]
fn write() {
let (_td, repo) = crate::test::repo_init();
let mut builder = repo.treebuilder(None).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 = repo.treebuilder(Some(&tree)).unwrap();
assert_eq!(builder.len(), 1);
}
#[test]
fn filter() {
let (_td, repo) = crate::test::repo_init();
let mut builder = repo.treebuilder(None).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(|_| true).unwrap();
assert_eq!(builder.len(), 3);
builder
.filter(|e| e.kind().unwrap() != ObjectType::Blob)
.unwrap();
assert_eq!(builder.len(), 2);
builder.filter(|_| false).unwrap();
assert_eq!(builder.len(), 0);
}
}