use libc::{c_int, c_uint, c_void, size_t};
use std::marker;
use std::ptr;
use std::slice;
use crate::util::Binding;
use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk};
pub enum PackBuilderStage {
AddingObjects,
Deltafication,
}
pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a;
pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a;
pub struct PackBuilder<'repo> {
raw: *mut raw::git_packbuilder,
progress: Option<Box<Box<ProgressCb<'repo>>>>,
_marker: marker::PhantomData<&'repo Repository>,
}
impl<'repo> PackBuilder<'repo> {
pub fn insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
let name = crate::opt_cstr(name)?;
unsafe {
try_call!(raw::git_packbuilder_insert(self.raw, id.raw(), name));
}
Ok(())
}
pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> {
unsafe {
try_call!(raw::git_packbuilder_insert_tree(self.raw, id.raw()));
}
Ok(())
}
pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> {
unsafe {
try_call!(raw::git_packbuilder_insert_commit(self.raw, id.raw()));
}
Ok(())
}
pub fn insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error> {
unsafe {
try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw()));
}
Ok(())
}
pub fn insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
let name = crate::opt_cstr(name)?;
unsafe {
try_call!(raw::git_packbuilder_insert_recur(self.raw, id.raw(), name));
}
Ok(())
}
pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> {
unsafe {
try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw));
}
Ok(())
}
pub fn foreach<F>(&mut self, mut cb: F) -> Result<(), Error>
where
F: FnMut(&[u8]) -> bool,
{
let mut cb = &mut cb as &mut ForEachCb<'_>;
let ptr = &mut cb as *mut _;
unsafe {
try_call!(raw::git_packbuilder_foreach(
self.raw,
foreach_c,
ptr as *mut _
));
}
Ok(())
}
pub fn set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error>
where
F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo,
{
let mut progress = Box::new(Box::new(progress) as Box<ProgressCb<'_>>);
let ptr = &mut *progress as *mut _;
let progress_c = Some(progress_c as raw::git_packbuilder_progress);
unsafe {
try_call!(raw::git_packbuilder_set_callbacks(
self.raw,
progress_c,
ptr as *mut _
));
}
self.progress = Some(progress);
Ok(())
}
pub fn unset_progress_callback(&mut self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_packbuilder_set_callbacks(
self.raw,
None,
ptr::null_mut()
));
self.progress = None;
}
Ok(())
}
pub fn set_threads(&mut self, threads: u32) -> u32 {
unsafe { raw::git_packbuilder_set_threads(self.raw, threads) }
}
pub fn object_count(&self) -> usize {
unsafe { raw::git_packbuilder_object_count(self.raw) }
}
pub fn written(&self) -> usize {
unsafe { raw::git_packbuilder_written(self.raw) }
}
pub fn hash(&self) -> Option<Oid> {
if self.object_count() == 0 {
unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) }
} else {
None
}
}
}
impl<'repo> Binding for PackBuilder<'repo> {
type Raw = *mut raw::git_packbuilder;
unsafe fn from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo> {
PackBuilder {
raw: ptr,
progress: None,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_packbuilder {
self.raw
}
}
impl<'repo> Drop for PackBuilder<'repo> {
fn drop(&mut self) {
unsafe {
raw::git_packbuilder_set_callbacks(self.raw, None, ptr::null_mut());
raw::git_packbuilder_free(self.raw);
}
}
}
impl Binding for PackBuilderStage {
type Raw = raw::git_packbuilder_stage_t;
unsafe fn from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage {
match raw {
raw::GIT_PACKBUILDER_ADDING_OBJECTS => PackBuilderStage::AddingObjects,
raw::GIT_PACKBUILDER_DELTAFICATION => PackBuilderStage::Deltafication,
_ => panic!("Unknown git diff binary kind"),
}
}
fn raw(&self) -> raw::git_packbuilder_stage_t {
match *self {
PackBuilderStage::AddingObjects => raw::GIT_PACKBUILDER_ADDING_OBJECTS,
PackBuilderStage::Deltafication => raw::GIT_PACKBUILDER_DELTAFICATION,
}
}
}
extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int {
unsafe {
let buf = slice::from_raw_parts(buf as *const u8, size as usize);
let r = panic::wrap(|| {
let data = data as *mut &mut ForEachCb<'_>;
(*data)(buf)
});
if r == Some(true) {
0
} else {
-1
}
}
}
extern "C" fn progress_c(
stage: raw::git_packbuilder_stage_t,
current: c_uint,
total: c_uint,
data: *mut c_void,
) -> c_int {
unsafe {
let stage = Binding::from_raw(stage);
let r = panic::wrap(|| {
let data = data as *mut Box<ProgressCb<'_>>;
(*data)(stage, current, total)
});
if r == Some(true) {
0
} else {
-1
}
}
}
#[cfg(test)]
mod tests {
use crate::{Buf, Oid, Repository};
use std::fs::File;
use std::path::Path;
fn commit(repo: &Repository) -> (Oid, Oid) {
let mut index = t!(repo.index());
let root = repo.path().parent().unwrap();
t!(File::create(&root.join("foo")));
t!(index.add_path(Path::new("foo")));
let tree_id = t!(index.write_tree());
let tree = t!(repo.find_tree(tree_id));
let sig = t!(repo.signature());
let head_id = t!(repo.refname_to_id("HEAD"));
let parent = t!(repo.find_commit(head_id));
let commit = t!(repo.commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]));
(commit, tree_id)
}
fn pack_header(len: u8) -> Vec<u8> {
[].iter()
.chain(b"PACK")
.chain(&[0, 0, 0, 2])
.chain(&[0, 0, 0, len])
.cloned()
.collect::<Vec<u8>>()
}
fn empty_pack_header() -> Vec<u8> {
pack_header(0)
.iter()
.chain(&[
0x02, 0x9d, 0x08, 0x82, 0x3b,
0xd8, 0xa8, 0xea, 0xb5, 0x10,
0xad, 0x6a, 0xc7, 0x5c, 0x82,
0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
])
.cloned()
.collect::<Vec<u8>>()
}
#[test]
fn smoke() {
let (_td, repo) = crate::test::repo_init();
let _builder = t!(repo.packbuilder());
}
#[test]
fn smoke_write_buf() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let mut buf = Buf::new();
t!(builder.write_buf(&mut buf));
assert!(builder.hash().unwrap().is_zero());
assert_eq!(&*buf, &*empty_pack_header());
}
#[test]
fn smoke_foreach() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let mut buf = Vec::<u8>::new();
t!(builder.foreach(|bytes| {
buf.extend(bytes);
true
}));
assert_eq!(&*buf, &*empty_pack_header());
}
#[test]
fn insert_write_buf() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let mut buf = Buf::new();
let (commit, _tree) = commit(&repo);
t!(builder.insert_object(commit, None));
assert_eq!(builder.object_count(), 1);
t!(builder.write_buf(&mut buf));
assert_eq!(&buf[0..12], &*pack_header(1));
}
#[test]
fn insert_tree_write_buf() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let mut buf = Buf::new();
let (_commit, tree) = commit(&repo);
t!(builder.insert_tree(tree));
assert_eq!(builder.object_count(), 2);
t!(builder.write_buf(&mut buf));
assert_eq!(&buf[0..12], &*pack_header(2));
}
#[test]
fn insert_commit_write_buf() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let mut buf = Buf::new();
let (commit, _tree) = commit(&repo);
t!(builder.insert_commit(commit));
assert_eq!(builder.object_count(), 3);
t!(builder.write_buf(&mut buf));
assert_eq!(&buf[0..12], &*pack_header(3));
}
#[test]
fn progress_callback() {
let mut progress_called = false;
{
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let (commit, _tree) = commit(&repo);
t!(builder.set_progress_callback(|_, _, _| {
progress_called = true;
true
}));
t!(builder.insert_commit(commit));
t!(builder.write_buf(&mut Buf::new()));
}
assert_eq!(progress_called, true);
}
#[test]
fn clear_progress_callback() {
let mut progress_called = false;
{
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let (commit, _tree) = commit(&repo);
t!(builder.set_progress_callback(|_, _, _| {
progress_called = true;
true
}));
t!(builder.unset_progress_callback());
t!(builder.insert_commit(commit));
t!(builder.write_buf(&mut Buf::new()));
}
assert_eq!(progress_called, false);
}
#[test]
fn set_threads() {
let (_td, repo) = crate::test::repo_init();
let mut builder = t!(repo.packbuilder());
let used = builder.set_threads(4);
assert!(used == 1 || used == 4);
}
}