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,
};
pub struct Odb<'repo> {
raw: *mut raw::git_odb,
_marker: marker::PhantomData<Object<'repo>>,
}
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> {
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))
}
}
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(),
))
}
}
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))
}
}
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(())
}
}
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))
}
}
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()))
}
}
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))
}
}
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,
})
}
pub fn exists(&self, oid: Oid) -> bool {
unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
}
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 }
}
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))
}
}
pub fn refresh(&self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_odb_refresh(self.raw));
Ok(())
}
}
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(())
}
}
pub fn add_new_mempack_backend<'odb>(
&'odb self,
priority: i32,
) -> Result<Mempack<'odb>, Error> {
unsafe {
let mut mempack = ptr::null_mut();
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))
}
}
}
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> {
pub fn kind(&self) -> ObjectType {
unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() }
}
pub fn len(&self) -> usize {
unsafe { raw::git_odb_object_size(self.raw) }
}
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;
}
}
pub fn id(&self) -> Oid {
unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) }
}
}
pub struct OdbReader<'repo> {
raw: *mut raw::git_odb_stream,
_marker: marker::PhantomData<Object<'repo>>,
}
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)
}
}
}
}
pub struct OdbWriter<'repo> {
raw: *mut raw::git_odb_stream,
_marker: marker::PhantomData<Object<'repo>>,
}
unsafe impl<'repo> Send for OdbWriter<'repo> {}
impl<'repo> OdbWriter<'repo> {
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>>>,
}
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> {
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)
}
}
}
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;
let (_td, repo) = crate::test::repo_init();
let odb = repo.odb().unwrap();
let mempack = odb.add_new_mempack_backend(1000).unwrap();
let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
assert!(!foo_file.exists());
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());
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());
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());
}
}