diff options
Diffstat (limited to 'vendor/git2/src/odb.rs')
-rw-r--r-- | vendor/git2/src/odb.rs | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/vendor/git2/src/odb.rs b/vendor/git2/src/odb.rs new file mode 100644 index 0000000..7f6da5e --- /dev/null +++ b/vendor/git2/src/odb.rs @@ -0,0 +1,729 @@ +use std::io; +use std::marker; +use std::ptr; +use std::slice; + +use std::ffi::CString; + +use libc::{c_char, c_int, c_uint, c_void, size_t}; + +use crate::panic; +use crate::util::Binding; +use crate::{ + raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress, +}; + +/// A structure to represent a git object database +pub struct Odb<'repo> { + raw: *mut raw::git_odb, + _marker: marker::PhantomData<Object<'repo>>, +} + +// `git_odb` uses locking and atomics internally. +unsafe impl<'repo> Send for Odb<'repo> {} +unsafe impl<'repo> Sync for Odb<'repo> {} + +impl<'repo> Binding for Odb<'repo> { + type Raw = *mut raw::git_odb; + + unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> { + Odb { + raw, + _marker: marker::PhantomData, + } + } + fn raw(&self) -> *mut raw::git_odb { + self.raw + } +} + +impl<'repo> Drop for Odb<'repo> { + fn drop(&mut self) { + unsafe { raw::git_odb_free(self.raw) } + } +} + +impl<'repo> Odb<'repo> { + /// Creates an object database without any backends. + pub fn new<'a>() -> Result<Odb<'a>, Error> { + crate::init(); + unsafe { + let mut out = ptr::null_mut(); + try_call!(raw::git_odb_new(&mut out)); + Ok(Odb::from_raw(out)) + } + } + + /// Create object database reading stream. + /// + /// Note that most backends do not support streaming reads because they store their objects as compressed/delta'ed blobs. + /// If the backend does not support streaming reads, use the `read` method instead. + pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> { + let mut out = ptr::null_mut(); + let mut size = 0usize; + let mut otype: raw::git_object_t = ObjectType::Any.raw(); + unsafe { + try_call!(raw::git_odb_open_rstream( + &mut out, + &mut size, + &mut otype, + self.raw, + oid.raw() + )); + Ok(( + OdbReader::from_raw(out), + size, + ObjectType::from_raw(otype).unwrap(), + )) + } + } + + /// Create object database writing stream. + /// + /// The type and final length of the object must be specified when opening the stream. + /// If the backend does not support streaming writes, use the `write` method instead. + pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> { + let mut out = ptr::null_mut(); + unsafe { + try_call!(raw::git_odb_open_wstream( + &mut out, + self.raw, + size as raw::git_object_size_t, + obj_type.raw() + )); + Ok(OdbWriter::from_raw(out)) + } + } + + /// Iterate over all objects in the object database.s + pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error> + where + C: FnMut(&Oid) -> bool, + { + unsafe { + let mut data = ForeachCbData { + callback: &mut callback, + }; + let cb: raw::git_odb_foreach_cb = Some(foreach_cb); + try_call!(raw::git_odb_foreach( + self.raw(), + cb, + &mut data as *mut _ as *mut _ + )); + Ok(()) + } + } + + /// Read an object from the database. + pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> { + let mut out = ptr::null_mut(); + unsafe { + try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw())); + Ok(OdbObject::from_raw(out)) + } + } + + /// Reads the header of an object from the database + /// without reading the full content. + pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> { + let mut size: usize = 0; + let mut kind_id: i32 = ObjectType::Any.raw(); + + unsafe { + try_call!(raw::git_odb_read_header( + &mut size as *mut size_t, + &mut kind_id as *mut raw::git_object_t, + self.raw, + oid.raw() + )); + + Ok((size, ObjectType::from_raw(kind_id).unwrap())) + } + } + + /// Write an object to the database. + pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> { + unsafe { + let mut out = raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }; + try_call!(raw::git_odb_write( + &mut out, + self.raw, + data.as_ptr() as *const c_void, + data.len(), + kind.raw() + )); + Ok(Oid::from_raw(&mut out)) + } + } + + /// Create stream for writing a pack file to the ODB + pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> { + let mut out = ptr::null_mut(); + let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb); + let progress_payload = Box::new(OdbPackwriterCb { cb: None }); + let progress_payload_ptr = Box::into_raw(progress_payload); + + unsafe { + try_call!(raw::git_odb_write_pack( + &mut out, + self.raw, + progress_cb, + progress_payload_ptr as *mut c_void + )); + } + + Ok(OdbPackwriter { + raw: out, + progress: Default::default(), + progress_payload_ptr, + }) + } + + /// Checks if the object database has an object. + pub fn exists(&self, oid: Oid) -> bool { + unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 } + } + + /// Checks if the object database has an object, with extended flags. + pub fn exists_ext(&self, oid: Oid, flags: OdbLookupFlags) -> bool { + unsafe { raw::git_odb_exists_ext(self.raw, oid.raw(), flags.bits() as c_uint) != 0 } + } + + /// Potentially finds an object that starts with the given prefix. + pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> { + unsafe { + let mut out = raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }; + try_call!(raw::git_odb_exists_prefix( + &mut out, + self.raw, + short_oid.raw(), + len + )); + Ok(Oid::from_raw(&out)) + } + } + + /// Refresh the object database. + /// This should never be needed, and is + /// provided purely for convenience. + /// The object database will automatically + /// refresh when an object is not found when + /// requested. + pub fn refresh(&self) -> Result<(), Error> { + unsafe { + try_call!(raw::git_odb_refresh(self.raw)); + Ok(()) + } + } + + /// Adds an alternate disk backend to the object database. + pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> { + unsafe { + let path = CString::new(path)?; + try_call!(raw::git_odb_add_disk_alternate(self.raw, path)); + 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<Mempack<'odb>, 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. +pub struct OdbObject<'a> { + raw: *mut raw::git_odb_object, + _marker: marker::PhantomData<Object<'a>>, +} + +impl<'a> Binding for OdbObject<'a> { + type Raw = *mut raw::git_odb_object; + + unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> { + OdbObject { + raw, + _marker: marker::PhantomData, + } + } + + fn raw(&self) -> *mut raw::git_odb_object { + self.raw + } +} + +impl<'a> Drop for OdbObject<'a> { + fn drop(&mut self) { + unsafe { raw::git_odb_object_free(self.raw) } + } +} + +impl<'a> OdbObject<'a> { + /// Get the object type. + pub fn kind(&self) -> ObjectType { + unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() } + } + + /// Get the object size. + pub fn len(&self) -> usize { + unsafe { raw::git_odb_object_size(self.raw) } + } + + /// Get the object data. + pub fn data(&self) -> &[u8] { + unsafe { + let size = self.len(); + let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8; + let buffer = slice::from_raw_parts(ptr, size); + return buffer; + } + } + + /// Get the object id. + pub fn id(&self) -> Oid { + unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) } + } +} + +/// A structure to represent a git ODB rstream +pub struct OdbReader<'repo> { + raw: *mut raw::git_odb_stream, + _marker: marker::PhantomData<Object<'repo>>, +} + +// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another +// thread and continuing to read will work. +unsafe impl<'repo> Send for OdbReader<'repo> {} + +impl<'repo> Binding for OdbReader<'repo> { + type Raw = *mut raw::git_odb_stream; + + unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> { + OdbReader { + raw, + _marker: marker::PhantomData, + } + } + fn raw(&self) -> *mut raw::git_odb_stream { + self.raw + } +} + +impl<'repo> Drop for OdbReader<'repo> { + fn drop(&mut self) { + unsafe { raw::git_odb_stream_free(self.raw) } + } +} + +impl<'repo> io::Read for OdbReader<'repo> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + unsafe { + let ptr = buf.as_ptr() as *mut c_char; + let len = buf.len(); + let res = raw::git_odb_stream_read(self.raw, ptr, len); + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Read error")) + } else { + Ok(len) + } + } + } +} + +/// A structure to represent a git ODB wstream +pub struct OdbWriter<'repo> { + raw: *mut raw::git_odb_stream, + _marker: marker::PhantomData<Object<'repo>>, +} + +// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another +// thread and continuing to write will work. +unsafe impl<'repo> Send for OdbWriter<'repo> {} + +impl<'repo> OdbWriter<'repo> { + /// Finish writing to an ODB stream + /// + /// This method can be used to finalize writing object to the database and get an identifier. + /// The object will take its final name and will be available to the odb. + /// This method will fail if the total number of received bytes differs from the size declared with odb_writer() + /// Attempting write after finishing will be ignored. + pub fn finalize(&mut self) -> Result<Oid, Error> { + let mut raw = raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }; + unsafe { + try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw)); + Ok(Binding::from_raw(&raw as *const _)) + } + } +} + +impl<'repo> Binding for OdbWriter<'repo> { + type Raw = *mut raw::git_odb_stream; + + unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> { + OdbWriter { + raw, + _marker: marker::PhantomData, + } + } + fn raw(&self) -> *mut raw::git_odb_stream { + self.raw + } +} + +impl<'repo> Drop for OdbWriter<'repo> { + fn drop(&mut self) { + unsafe { raw::git_odb_stream_free(self.raw) } + } +} + +impl<'repo> io::Write for OdbWriter<'repo> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + unsafe { + let ptr = buf.as_ptr() as *const c_char; + let len = buf.len(); + let res = raw::git_odb_stream_write(self.raw, ptr, len); + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Write error")) + } else { + Ok(buf.len()) + } + } + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub(crate) struct OdbPackwriterCb<'repo> { + pub(crate) cb: Option<Box<IndexerProgress<'repo>>>, +} + +/// A stream to write a packfile to the ODB +pub struct OdbPackwriter<'repo> { + raw: *mut raw::git_odb_writepack, + progress: raw::git_indexer_progress, + progress_payload_ptr: *mut OdbPackwriterCb<'repo>, +} + +impl<'repo> OdbPackwriter<'repo> { + /// Finish writing the packfile + pub fn commit(&mut self) -> Result<i32, Error> { + unsafe { + let writepack = &*self.raw; + let res = match writepack.commit { + Some(commit) => commit(self.raw, &mut self.progress), + None => -1, + }; + + if res < 0 { + Err(Error::last_error(res).unwrap()) + } else { + Ok(res) + } + } + } + + /// The callback through which progress is monitored. Be aware that this is + /// called inline, so performance may be affected. + pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo> + where + F: FnMut(Progress<'_>) -> bool + 'repo, + { + let progress_payload = + unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) }; + + progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>); + self + } +} + +impl<'repo> io::Write for OdbPackwriter<'repo> { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + unsafe { + let ptr = buf.as_ptr() as *mut c_void; + let len = buf.len(); + + let writepack = &*self.raw; + let res = match writepack.append { + Some(append) => append(self.raw, ptr, len, &mut self.progress), + None => -1, + }; + + if res < 0 { + Err(io::Error::new(io::ErrorKind::Other, "Write error")) + } else { + Ok(buf.len()) + } + } + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl<'repo> Drop for OdbPackwriter<'repo> { + fn drop(&mut self) { + unsafe { + let writepack = &*self.raw; + match writepack.free { + Some(free) => free(self.raw), + None => (), + }; + + drop(Box::from_raw(self.progress_payload_ptr)); + } + } +} + +pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a; + +struct ForeachCbData<'a> { + pub callback: &'a mut ForeachCb<'a>, +} + +extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int { + panic::wrap(|| unsafe { + let data = &mut *(payload as *mut ForeachCbData<'_>); + let res = { + let callback = &mut data.callback; + callback(&Binding::from_raw(id)) + }; + + if res { + 0 + } else { + 1 + } + }) + .unwrap_or(1) +} + +pub(crate) extern "C" fn write_pack_progress_cb( + stats: *const raw::git_indexer_progress, + payload: *mut c_void, +) -> c_int { + let ok = panic::wrap(|| unsafe { + let payload = &mut *(payload as *mut OdbPackwriterCb<'_>); + + let callback = match payload.cb { + Some(ref mut cb) => cb, + None => return true, + }; + + let progress: Progress<'_> = Binding::from_raw(stats); + callback(progress) + }); + if ok == Some(true) { + 0 + } else { + -1 + } +} + +#[cfg(test)] +mod tests { + use crate::{Buf, ObjectType, Oid, Repository}; + use std::io::prelude::*; + use tempfile::TempDir; + + #[test] + fn read() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let id = repo.blob(&dat).unwrap(); + let db = repo.odb().unwrap(); + let obj = db.read(id).unwrap(); + let data = obj.data(); + let size = obj.len(); + assert_eq!(size, 5); + assert_eq!(dat, data); + assert_eq!(id, obj.id()); + } + + #[test] + fn read_header() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let id = repo.blob(&dat).unwrap(); + let db = repo.odb().unwrap(); + let (size, kind) = db.read_header(id).unwrap(); + + assert_eq!(size, 5); + assert_eq!(kind, ObjectType::Blob); + } + + #[test] + fn write() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let db = repo.odb().unwrap(); + let id = db.write(ObjectType::Blob, &dat).unwrap(); + let blob = repo.find_blob(id).unwrap(); + assert_eq!(blob.content(), dat); + } + + #[test] + fn writer() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let db = repo.odb().unwrap(); + let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap(); + let wl = ws.write(&dat[0..3]).unwrap(); + assert_eq!(wl, 3); + let wl = ws.write(&dat[3..5]).unwrap(); + assert_eq!(wl, 2); + let id = ws.finalize().unwrap(); + let blob = repo.find_blob(id).unwrap(); + assert_eq!(blob.content(), dat); + } + + #[test] + fn exists() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let db = repo.odb().unwrap(); + let id = db.write(ObjectType::Blob, &dat).unwrap(); + assert!(db.exists(id)); + } + + #[test] + fn exists_prefix() { + let td = TempDir::new().unwrap(); + let repo = Repository::init(td.path()).unwrap(); + let dat = [4, 3, 5, 6, 9]; + let db = repo.odb().unwrap(); + let id = db.write(ObjectType::Blob, &dat).unwrap(); + let id_prefix_str = &id.to_string()[0..10]; + let id_prefix = Oid::from_str(id_prefix_str).unwrap(); + let found_oid = db.exists_prefix(id_prefix, 10).unwrap(); + assert_eq!(found_oid, id); + } + + #[test] + fn packwriter() { + let (_td, repo_source) = crate::test::repo_init(); + let (_td, repo_target) = crate::test::repo_init(); + let mut builder = t!(repo_source.packbuilder()); + let mut buf = Buf::new(); + let (commit_source_id, _tree) = crate::test::commit(&repo_source); + t!(builder.insert_object(commit_source_id, None)); + t!(builder.write_buf(&mut buf)); + let db = repo_target.odb().unwrap(); + let mut packwriter = db.packwriter().unwrap(); + packwriter.write(&buf).unwrap(); + packwriter.commit().unwrap(); + let commit_target = repo_target.find_commit(commit_source_id).unwrap(); + assert_eq!(commit_target.id(), commit_source_id); + } + + #[test] + fn packwriter_progress() { + let mut progress_called = false; + { + let (_td, repo_source) = crate::test::repo_init(); + let (_td, repo_target) = crate::test::repo_init(); + let mut builder = t!(repo_source.packbuilder()); + let mut buf = Buf::new(); + let (commit_source_id, _tree) = crate::test::commit(&repo_source); + t!(builder.insert_object(commit_source_id, None)); + t!(builder.write_buf(&mut buf)); + let db = repo_target.odb().unwrap(); + let mut packwriter = db.packwriter().unwrap(); + packwriter.progress(|_| { + progress_called = true; + true + }); + packwriter.write(&buf).unwrap(); + packwriter.commit().unwrap(); + } + 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()); + } +} |