diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs index 1ade9be838..bc8a8c300a 100644 --- a/libgit2-sys/lib.rs +++ b/libgit2-sys/lib.rs @@ -73,6 +73,7 @@ pub enum git_diff_stats {} pub enum git_reflog {} pub enum git_reflog_entry {} pub enum git_describe_result {} +pub enum git_packbuilder {} #[repr(C)] pub struct git_revspec { @@ -363,8 +364,8 @@ pub type git_cred_acquire_cb = extern fn(*mut *mut git_cred, c_uint, *mut c_void) -> c_int; pub type git_transfer_progress_cb = extern fn(*const git_transfer_progress, *mut c_void) -> c_int; -pub type git_packbuilder_progress = extern fn(c_int, c_uint, c_uint, - *mut c_void) -> c_int; +pub type git_packbuilder_progress = extern fn(git_packbuilder_stage_t, c_uint, + c_uint, *mut c_void) -> c_int; pub type git_push_transfer_progress = extern fn(c_uint, c_uint, size_t, *mut c_void) -> c_int; pub type git_transport_certificate_check_cb = extern fn(*mut git_cert, @@ -1259,6 +1260,16 @@ pub struct git_describe_format_options { pub dirty_suffix: *const c_char, } +git_enum! { + pub enum git_packbuilder_stage_t { + GIT_PACKBUILDER_ADDING_OBJECTS, + GIT_PACKBUILDER_DELTAFICATION, + } +} + +pub type git_packbuilder_foreach_cb = extern fn(*const c_void, size_t, + *mut c_void) -> c_int; + /// Initialize openssl for the libgit2 library #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), feature = "https"))] pub fn openssl_init() { @@ -2442,6 +2453,41 @@ extern { message: *const c_char, strip_comments: c_int, comment_char: c_char) -> c_int; + + // packbuilder + pub fn git_packbuilder_new(out: *mut *mut git_packbuilder, + repo: *mut git_repository) -> c_int; + pub fn git_packbuilder_set_threads(pb: *mut git_packbuilder, + n: c_uint) -> c_uint; + pub fn git_packbuilder_insert(pb: *mut git_packbuilder, + id: *const git_oid, + name: *const c_char) -> c_int; + pub fn git_packbuilder_insert_tree(pb: *mut git_packbuilder, + id: *const git_oid) -> c_int; + pub fn git_packbuilder_insert_commit(pb: *mut git_packbuilder, + id: *const git_oid) -> c_int; + pub fn git_packbuilder_insert_walk(pb: *mut git_packbuilder, + walk: *mut git_revwalk) -> c_int; + pub fn git_packbuilder_insert_recur(pb: *mut git_packbuilder, + id: *const git_oid, + name: *const c_char) -> c_int; + pub fn git_packbuilder_write_buf(buf: *mut git_buf, + pb: *mut git_packbuilder) -> c_int; + pub fn git_packbuilder_write(pb: *mut git_packbuilder, + path: *const c_char, + mode: c_uint, + progress_cb: Option, + progress_cb_payload: *mut c_void) -> c_int; + pub fn git_packbuilder_hash(pb: *mut git_packbuilder) -> *const git_oid; + pub fn git_packbuilder_foreach(pb: *mut git_packbuilder, + cb: git_packbuilder_foreach_cb, + payload: *mut c_void) -> c_int; + pub fn git_packbuilder_object_count(pb: *mut git_packbuilder) -> u32; + pub fn git_packbuilder_written(pb: *mut git_packbuilder) -> u32; + pub fn git_packbuilder_set_callbacks(pb: *mut git_packbuilder, + progress_cb: Option, + progress_cb_payload: *mut c_void) -> c_int; + pub fn git_packbuilder_free(pb: *mut git_packbuilder); } #[test] diff --git a/src/lib.rs b/src/lib.rs index b71c591d61..5cb836e7f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ pub use message::{message_prettify, DEFAULT_COMMENT_CHAR}; pub use note::{Note, Notes}; pub use object::Object; pub use oid::Oid; +pub use packbuilder::{PackBuilder, PackBuilderStage}; pub use pathspec::{Pathspec, PathspecMatchList, PathspecFailedEntries}; pub use pathspec::{PathspecDiffEntries, PathspecEntries}; pub use reference::{Reference, References, ReferenceNames}; @@ -466,6 +467,7 @@ mod message; mod note; mod object; mod oid; +mod packbuilder; mod pathspec; mod reference; mod reflog; diff --git a/src/packbuilder.rs b/src/packbuilder.rs new file mode 100644 index 0000000000..f21e02a983 --- /dev/null +++ b/src/packbuilder.rs @@ -0,0 +1,361 @@ +use std::marker; +use std::slice; +use std::ptr; +use libc::{c_int, c_uint, c_void, size_t}; + +use {raw, panic, Repository, Error, Oid, Revwalk, Buf}; +use util::Binding; + +/// Stages that are reported by the PackBuilder progress callback. +pub enum PackBuilderStage { + /// Adding objects to the pack + AddingObjects, + /// Deltafication of the pack + Deltafication, +} + +pub type ProgressCb<'a> = FnMut(PackBuilderStage, u32, u32) -> bool + 'a; +pub type ForEachCb<'a> = FnMut(&[u8]) -> bool + 'a; + +/// A builder for creating a packfile +pub struct PackBuilder<'repo> { + raw: *mut raw::git_packbuilder, + _marker: marker::PhantomData<&'repo Repository>, +} + +impl<'repo> PackBuilder<'repo> { + /// Insert a single object. For an optimal pack it's mandatory to insert + /// objects in recency order, commits followed by trees and blobs. + pub fn insert_object(&mut self, + id: Oid, + name: Option<&str>) + -> Result<(), Error> { + let name = try!(::opt_cstr(name)); + unsafe { + try_call!(raw::git_packbuilder_insert(self.raw, &*id.raw(), name)); + } + Ok(()) + } + + /// Insert a root tree object. This will add the tree as well as all + /// referenced trees and blobs. + pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> { + unsafe { + try_call!(raw::git_packbuilder_insert_tree(self.raw, &*id.raw())); + } + Ok(()) + } + + /// Insert a commit object. This will add a commit as well as the completed + /// referenced tree. + pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> { + unsafe { + try_call!(raw::git_packbuilder_insert_commit(self.raw, &*id.raw())); + } + Ok(()) + } + + /// Insert objects as given by the walk. Those commits and all objects they + /// reference will be inserted into the packbuilder. + pub fn insert_walk(&mut self, walk: &mut Revwalk) -> Result<(), Error> { + unsafe { + try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw())); + } + Ok(()) + } + + /// Recursively insert an object and its referenced objects. Insert the + /// object as well as any object it references. + pub fn insert_recursive(&mut self, + id: Oid, + name: Option<&str>) + -> Result<(), Error> { + let name = try!(::opt_cstr(name)); + unsafe { + try_call!(raw::git_packbuilder_insert_recur(self.raw, + &*id.raw(), + name)); + } + Ok(()) + } + + /// Write the contents of the packfile to an in-memory buffer. The contents + /// of the buffer will become a valid packfile, even though there will be + /// no attached index. + /// + /// `progress` will be called with progress information during pack + /// building. Be aware that this is called inline with pack building + /// operations, so performance may be affected. + pub fn write_buf(&mut self, + buf: &mut Buf, + progress: Option<&mut ProgressCb>) + -> Result<(), Error> { + let has_progress = progress.is_some(); + if let Some(mut progress) = progress { + 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 _)); + } + } + unsafe { + try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw)); + } + if has_progress { + unsafe { + try_call!(raw::git_packbuilder_set_callbacks(self.raw, + None, + ptr::null_mut())); + } + } + Ok(()) + } + + /// Create the new pack and pass each object to the callback. + /// + /// `progress` will be called with progress information during pack + /// building. Be aware that this is called inline with pack building + /// operations, so performance may be affected. + pub fn foreach(&mut self, + mut cb: &mut ForEachCb, + progress: Option<&mut ProgressCb>) + -> Result<(), Error> { + let has_progress = progress.is_some(); + if let Some(mut progress) = progress { + 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 _)); + } + } + let ptr = &mut cb as *mut _; + unsafe { + try_call!(raw::git_packbuilder_foreach(self.raw, + foreach_c, + ptr as *mut _)); + } + if has_progress { + unsafe { + try_call!(raw::git_packbuilder_set_callbacks(self.raw, + None, + ptr::null_mut())); + } + } + Ok(()) + } + + /// Get the total number of objects the packbuilder will write out. + pub fn object_count(&self) -> usize { + unsafe { raw::git_packbuilder_object_count(self.raw) as usize } + } + + /// Get the number of objects the packbuilder has already written out. + pub fn written(&self) -> usize { + unsafe { raw::git_packbuilder_written(self.raw) as usize } + } + + /// Get the packfile's hash. A packfile's name is derived from the sorted + /// hashing of all object names. This is only correct after the packfile + /// has been written. + pub fn hash(&self) -> Option { + 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, + _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_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 &mut ProgressCb; + (*data)(stage, current, total) + }); + if r == Some(true) { + 0 + } else { + -1 + } + } +} + +#[cfg(test)] +mod tests { + use std::fs::File; + use std::path::Path; + use {Buf, Repository, Oid}; + + 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 { + [].into_iter() + .chain(b"PACK") // signature + .chain(&[0, 0, 0, 2]) // version number + .chain(&[0, 0, 0, len]) // number of objects + .cloned().collect::>() + } + + #[test] + fn smoke() { + let (_td, repo) = ::test::repo_init(); + let _builder = t!(repo.packbuilder()); + } + + #[test] + fn smoke_write_buf() { + let (_td, repo) = ::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let mut buf = Buf::new(); + t!(builder.write_buf(&mut buf, None)); + assert!(builder.hash().unwrap().is_zero()); + let expected = pack_header(0).iter() + .chain(&[0x02, 0x9d, 0x08, 0x82, 0x3b, // ^ + 0xd8, 0xa8, 0xea, 0xb5, 0x10, // | SHA-1 of the zero + 0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header + 0x3c, 0xfd, 0x3e, 0xd3, 0x1e]) // v + .cloned().collect::>(); + assert_eq!(&*buf, &*expected); + } + + #[test] + fn insert_write_buf() { + let (_td, repo) = ::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, None)); + // Just check that the correct number of objects are written + assert_eq!(&buf[0..12], &*pack_header(1)); + } + + #[test] + fn insert_tree_write_buf() { + let (_td, repo) = ::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let mut buf = Buf::new(); + let (_commit, tree) = commit(&repo); + // will insert the tree itself and the blob, 2 objects + t!(builder.insert_tree(tree)); + assert_eq!(builder.object_count(), 2); + t!(builder.write_buf(&mut buf, None)); + // Just check that the correct number of objects are written + assert_eq!(&buf[0..12], &*pack_header(2)); + } + + #[test] + fn insert_commit_write_buf() { + let (_td, repo) = ::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let mut buf = Buf::new(); + let (commit, _tree) = commit(&repo); + // will insert the commit, its tree and the blob, 3 objects + t!(builder.insert_commit(commit)); + assert_eq!(builder.object_count(), 3); + t!(builder.write_buf(&mut buf, None)); + // Just check that the correct number of objects are written + assert_eq!(&buf[0..12], &*pack_header(3)); + } + + #[test] + fn progress_callback() { + let (_td, repo) = ::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let (commit, _tree) = commit(&repo); + t!(builder.insert_commit(commit)); + let mut progress_called = false; + t!(builder.write_buf(&mut Buf::new(), + Some(&mut |_, _, _| { + progress_called = true; + true + }))); + assert_eq!(progress_called, true); + } +} diff --git a/src/repo.rs b/src/repo.rs index d2901ed69d..26c8a9fb0c 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -12,7 +12,7 @@ use {Branches, BranchType, Index, Config, Oid, Blob, Branch, Commit, Tree}; use {AnnotatedCommit, MergeOptions, SubmoduleIgnore, SubmoduleStatus}; use {ObjectType, Tag, Note, Notes, StatusOptions, Statuses, Status, Revwalk}; use {RevparseMode, RepositoryInitMode, Reflog, IntoCString, Describe}; -use {DescribeOptions, TreeBuilder, Diff, DiffOptions}; +use {DescribeOptions, TreeBuilder, Diff, DiffOptions, PackBuilder}; use build::{RepoBuilder, CheckoutBuilder}; use string_array::StringArray; use oid_array::OidArray; @@ -1531,6 +1531,16 @@ impl Repository { Ok(Binding::from_raw(ret)) } } + + /// Create a PackBuilder + pub fn packbuilder(&self) -> Result { + let mut ret = 0 as *mut raw::git_packbuilder; + unsafe { + try_call!(raw::git_packbuilder_new(&mut ret, self.raw())); + Ok(Binding::from_raw(ret)) + } + } + } impl Binding for Repository {