diff options
Diffstat (limited to 'vendor/git2/src/packbuilder.rs')
-rw-r--r-- | vendor/git2/src/packbuilder.rs | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/vendor/git2/src/packbuilder.rs b/vendor/git2/src/packbuilder.rs new file mode 100644 index 0000000..9b93e76 --- /dev/null +++ b/vendor/git2/src/packbuilder.rs @@ -0,0 +1,413 @@ +use libc::{c_int, c_uint, c_void, size_t}; +use std::marker; +use std::ptr; +use std::slice; +use std::str; + +use crate::util::Binding; +use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk}; + +#[derive(PartialEq, Eq, Clone, Debug, Copy)] +/// 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> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a; +pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a; + +/// A builder for creating a packfile +pub struct PackBuilder<'repo> { + raw: *mut raw::git_packbuilder, + _progress: Option<Box<Box<ProgressCb<'repo>>>>, + _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 = crate::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 = crate::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. + pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> { + unsafe { + try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw)); + } + Ok(()) + } + + /// Create the new pack and pass each object to the callback. + 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 _; + let foreach: raw::git_packbuilder_foreach_cb = Some(foreach_c); + unsafe { + try_call!(raw::git_packbuilder_foreach( + self.raw, + foreach, + ptr as *mut _ + )); + } + Ok(()) + } + + /// `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. + /// + /// There can only be one progress callback attached, this will replace any + /// existing one. See `unset_progress_callback` to remove the current + /// progress callback without attaching a new one. + 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: raw::git_packbuilder_progress = Some(progress_c); + unsafe { + try_call!(raw::git_packbuilder_set_callbacks( + self.raw, + progress_c, + ptr as *mut _ + )); + } + self._progress = Some(progress); + Ok(()) + } + + /// Remove the current progress callback. See `set_progress_callback` to + /// set the progress callback. + 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(()) + } + + /// Set the number of threads to be used. + /// + /// Returns the number of threads to be used. + pub fn set_threads(&mut self, threads: u32) -> u32 { + unsafe { raw::git_packbuilder_set_threads(self.raw, threads) } + } + + /// 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) } + } + + /// Get the number of objects the packbuilder has already written out. + pub fn written(&self) -> usize { + unsafe { raw::git_packbuilder_written(self.raw) } + } + + /// 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. + #[deprecated = "use `name()` to retrieve the filename"] + #[allow(deprecated)] + pub fn hash(&self) -> Option<Oid> { + if self.object_count() == 0 { + unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) } + } else { + None + } + } + + /// Get the unique name for the resulting packfile. + /// + /// The packfile's name is derived from the packfile's content. This is only + /// correct after the packfile has been written. + /// + /// Returns `None` if the packfile has not been written or if the name is + /// not valid utf-8. + pub fn name(&self) -> Option<&str> { + self.name_bytes().and_then(|s| str::from_utf8(s).ok()) + } + + /// Get the unique name for the resulting packfile, in bytes. + /// + /// The packfile's name is derived from the packfile's content. This is only + /// correct after the packfile has been written. + pub fn name_bytes(&self) -> Option<&[u8]> { + unsafe { crate::opt_bytes(self, raw::git_packbuilder_name(self.raw)) } + } +} + +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; + + fn pack_header(len: u8) -> Vec<u8> { + [].iter() + .chain(b"PACK") // signature + .chain(&[0, 0, 0, 2]) // version number + .chain(&[0, 0, 0, len]) // number of objects + .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, // | SHA-1 of the zero + 0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header + 0x3c, 0xfd, 0x3e, 0xd3, 0x1e, + ]) // v + .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)); + #[allow(deprecated)] + { + assert!(builder.hash().unwrap().is_zero()); + } + assert!(builder.name().is_none()); + 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) = crate::test::commit(&repo); + t!(builder.insert_object(commit, None)); + assert_eq!(builder.object_count(), 1); + t!(builder.write_buf(&mut buf)); + // 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) = crate::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let mut buf = Buf::new(); + let (_commit, tree) = crate::test::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)); + // 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) = crate::test::repo_init(); + let mut builder = t!(repo.packbuilder()); + let mut buf = Buf::new(); + let (commit, _tree) = crate::test::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)); + // Just check that the correct number of objects are written + 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) = crate::test::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) = crate::test::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); + // Will be 1 if not compiled with threading. + assert!(used == 1 || used == 4); + } +} |