diff --git a/src/lib.rs b/src/lib.rs index 262486c61e..0939ccd2b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,7 @@ pub use crate::index::{ Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath, }; pub use crate::indexer::{IndexerProgress, Progress}; +pub use crate::mempack::Mempack; pub use crate::merge::{AnnotatedCommit, MergeOptions}; pub use crate::message::{message_prettify, DEFAULT_COMMENT_CHAR}; pub use crate::note::{Note, Notes}; @@ -654,6 +655,7 @@ mod diff; mod error; mod index; mod indexer; +mod mempack; mod merge; mod message; mod note; diff --git a/src/mempack.rs b/src/mempack.rs new file mode 100644 index 0000000000..2de9a27813 --- /dev/null +++ b/src/mempack.rs @@ -0,0 +1,49 @@ +use std::marker; + +use crate::util::Binding; +use crate::{raw, Buf, Error, Odb, Repository}; + +/// A structure to represent a mempack backend for the object database. The +/// Mempack is bound to the Odb that it was created from, and cannot outlive +/// that Odb. +pub struct Mempack<'odb> { + raw: *mut raw::git_odb_backend, + _marker: marker::PhantomData<&'odb Odb<'odb>>, +} + +impl<'odb> Binding for Mempack<'odb> { + type Raw = *mut raw::git_odb_backend; + + unsafe fn from_raw(raw: *mut raw::git_odb_backend) -> Mempack<'odb> { + Mempack { + raw: raw, + _marker: marker::PhantomData, + } + } + + fn raw(&self) -> *mut raw::git_odb_backend { + self.raw + } +} + +// We don't need to implement `Drop` for Mempack because it is owned by the +// odb to which it is attached, and that will take care of freeing the mempack +// and associated memory. + +impl<'odb> Mempack<'odb> { + /// Dumps the contents of the mempack into the provided buffer. + pub fn dump(&self, repo: &Repository, buf: &mut Buf) -> Result<(), Error> { + unsafe { + try_call!(raw::git_mempack_dump(buf.raw(), repo.raw(), self.raw)); + } + Ok(()) + } + + /// Clears all data in the mempack. + pub fn reset(&self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_mempack_reset(self.raw)); + } + Ok(()) + } +} diff --git a/src/odb.rs b/src/odb.rs index 9ee034ac21..de3b42659a 100644 --- a/src/odb.rs +++ b/src/odb.rs @@ -10,7 +10,7 @@ use libc::{c_char, c_int, c_void, size_t}; use crate::panic; use crate::util::Binding; -use crate::{raw, Error, IndexerProgress, Object, ObjectType, Oid, Progress}; +use crate::{raw, Error, IndexerProgress, Mempack, Object, ObjectType, Oid, Progress}; /// A structure to represent a git object database pub struct Odb<'repo> { @@ -218,6 +218,47 @@ impl<'repo> Odb<'repo> { Ok(()) } } + + /// Create a new mempack backend, and add it to this odb with the given + /// priority. Higher values give the backend higher precedence. The default + /// loose and pack backends have priorities 1 and 2 respectively (hard-coded + /// in libgit2). A reference to the new mempack backend is returned on + /// success. The lifetime of the backend must be contained within the + /// lifetime of this odb, since deletion of the odb will also result in + /// deletion of the mempack backend. + /// + /// Here is an example that fails to compile because it tries to hold the + /// mempack reference beyond the odb's lifetime: + /// + /// ```compile_fail + /// use git2::Odb; + /// let mempack = { + /// let odb = Odb::new().unwrap(); + /// odb.add_new_mempack_backend(1000).unwrap() + /// }; + /// ``` + pub fn add_new_mempack_backend<'odb>( + &'odb self, + priority: i32, + ) -> Result, Error> { + unsafe { + let mut mempack = ptr::null_mut(); + // The mempack backend object in libgit2 is only ever freed by an + // odb that has the backend in its list. So to avoid potentially + // leaking the mempack backend, this API ensures that the backend + // is added to the odb before returning it. The lifetime of the + // mempack is also bound to the lifetime of the odb, so that users + // can't end up with a dangling reference to a mempack object that + // was actually freed when the odb was destroyed. + try_call!(raw::git_mempack_new(&mut mempack)); + try_call!(raw::git_odb_add_backend( + self.raw, + mempack, + priority as c_int + )); + Ok(Mempack::from_raw(mempack)) + } + } } /// An object from the Object Database. @@ -626,4 +667,45 @@ mod tests { } assert_eq!(progress_called, true); } + + #[test] + fn write_with_mempack() { + use crate::{Buf, ResetType}; + use std::io::Write; + use std::path::Path; + + // Create a repo, add a mempack backend + let (_td, repo) = crate::test::repo_init(); + let odb = repo.odb().unwrap(); + let mempack = odb.add_new_mempack_backend(1000).unwrap(); + + // Sanity check that foo doesn't exist initially + let foo_file = Path::new(repo.workdir().unwrap()).join("foo"); + assert!(!foo_file.exists()); + + // Make a commit that adds foo. This writes new stuff into the mempack + // backend. + let (oid1, _id) = crate::test::commit(&repo); + let commit1 = repo.find_commit(oid1).unwrap(); + t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); + assert!(foo_file.exists()); + + // Dump the mempack modifications into a buf, and reset it. This "erases" + // commit-related objects from the repository. Ensure the commit appears + // to have become invalid, by checking for failure in `reset --hard`. + let mut buf = Buf::new(); + mempack.dump(&repo, &mut buf).unwrap(); + mempack.reset().unwrap(); + assert!(repo + .reset(commit1.as_object(), ResetType::Hard, None) + .is_err()); + + // Write the buf into a packfile in the repo. This brings back the + // missing objects, and we verify everything is good again. + let mut packwriter = odb.packwriter().unwrap(); + packwriter.write(&buf).unwrap(); + packwriter.commit().unwrap(); + t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); + assert!(foo_file.exists()); + } } diff --git a/src/repo.rs b/src/repo.rs index 0a8fc088c8..5559d60935 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1055,6 +1055,14 @@ impl Repository { } } + /// Override the object database for this repository + pub fn set_odb(&self, odb: &Odb<'_>) -> Result<(), Error> { + unsafe { + try_call!(raw::git_repository_set_odb(self.raw(), odb.raw())); + } + Ok(()) + } + /// Create a new branch pointing at a target commit /// /// A new direct reference will be created pointing to this target commit.