-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add bindings to git_indexer #911
Changes from 1 commit
f862341
2e95ccb
318bf82
a3067e7
f36cf67
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
use std::marker; | ||
use std::ffi::CStr; | ||
use std::mem::MaybeUninit; | ||
use std::path::Path; | ||
use std::{io, marker, mem, ptr}; | ||
|
||
use crate::raw; | ||
use libc::c_void; | ||
|
||
use crate::odb::{write_pack_progress_cb, OdbPackwriterCb}; | ||
use crate::util::Binding; | ||
use crate::{raw, Error, IntoCString, Odb}; | ||
|
||
/// Struct representing the progress by an in-flight transfer. | ||
pub struct Progress<'a> { | ||
|
@@ -94,3 +100,159 @@ impl<'a> Binding for Progress<'a> { | |
)] | ||
#[allow(dead_code)] | ||
pub type TransportProgress<'a> = IndexerProgress<'a>; | ||
|
||
/// A stream to write and index a packfile | ||
/// | ||
/// This is equivalent to [`crate::OdbPackwriter`], but allows to store the pack | ||
/// and index at an arbitrary path. It also does not require access to an object | ||
/// database if, and only if, the pack file is self-contained (i.e. not "thin"). | ||
pub struct Indexer<'odb> { | ||
raw: *mut raw::git_indexer, | ||
progress: MaybeUninit<raw::git_indexer_progress>, | ||
progress_payload_ptr: *mut OdbPackwriterCb<'odb>, | ||
} | ||
|
||
impl<'a> Indexer<'a> { | ||
/// Create a new indexer | ||
/// | ||
/// The [`Odb`] is used to resolve base objects when fixing thin packs. It | ||
/// can be `None` if no thin pack is expected, in which case missing bases | ||
/// will result in an error. | ||
/// | ||
/// `mode` is the permissions to use for the output files, use `0` for defaults. | ||
/// | ||
/// If `verify` is `false`, the indexer will bypass object connectivity checks. | ||
pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> { | ||
let path = path.into_c_string()?; | ||
|
||
let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut); | ||
|
||
let mut out = ptr::null_mut(); | ||
let progress = MaybeUninit::uninit(); | ||
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 { | ||
let mut opts = mem::zeroed(); | ||
try_call!(raw::git_indexer_options_init( | ||
&mut opts, | ||
raw::GIT_INDEXER_OPTIONS_VERSION | ||
)); | ||
opts.progress_cb = progress_cb; | ||
opts.progress_cb_payload = progress_payload_ptr as *mut c_void; | ||
opts.verify = verify.into(); | ||
|
||
try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts)); | ||
} | ||
|
||
Ok(Self { | ||
raw: out, | ||
progress, | ||
progress_payload_ptr, | ||
}) | ||
} | ||
|
||
/// Finalize the pack and index | ||
/// | ||
/// Resolves any pending deltas and writes out the index file. The returned | ||
/// string is the hexadecimal checksum of the packfile, which is also used | ||
/// to name the pack and index files (`pack-<checksum>.pack` and | ||
/// `pack-<checksum>.idx` respectively). | ||
pub fn commit(mut self) -> Result<String, Error> { | ||
unsafe { | ||
try_call!(raw::git_indexer_commit( | ||
self.raw, | ||
self.progress.as_mut_ptr() | ||
)); | ||
|
||
let name = CStr::from_ptr(raw::git_indexer_name(self.raw)); | ||
Ok(name.to_str().expect("pack name not utf8").to_owned()) | ||
} | ||
} | ||
|
||
/// 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 Self | ||
where | ||
F: FnMut(Progress<'_>) -> bool + 'a, | ||
{ | ||
let progress_payload = | ||
unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) }; | ||
progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>); | ||
|
||
self | ||
} | ||
} | ||
|
||
impl io::Write for Indexer<'_> { | ||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||
unsafe { | ||
let ptr = buf.as_ptr() as *mut c_void; | ||
let len = buf.len(); | ||
|
||
let res = raw::git_indexer_append(self.raw, ptr, len, self.progress.as_mut_ptr()); | ||
|
||
if res < 0 { | ||
Err(io::Error::new(io::ErrorKind::Other, "Write error")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unfortunate that it erases the libgit2 error. I assume you are just copying this from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think That being said, |
||
} else { | ||
Ok(buf.len()) | ||
} | ||
} | ||
} | ||
|
||
fn flush(&mut self) -> io::Result<()> { | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl Drop for Indexer<'_> { | ||
fn drop(&mut self) { | ||
unsafe { | ||
raw::git_indexer_free(self.raw); | ||
drop(Box::from_raw(self.progress_payload_ptr)) | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::{Buf, Indexer}; | ||
use std::io::prelude::*; | ||
|
||
#[test] | ||
fn indexer() { | ||
let (_td, repo_source) = crate::test::repo_init(); | ||
let (_td, repo_target) = crate::test::repo_init(); | ||
|
||
let mut progress_called = false; | ||
|
||
// Create an in-memory packfile | ||
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)); | ||
|
||
// Write it to the standard location in the target repo, but via indexer | ||
let odb = repo_source.odb().unwrap(); | ||
let mut indexer = Indexer::new( | ||
Some(&odb), | ||
repo_target.path().join("objects").join("pack").as_path(), | ||
0o644, | ||
true, | ||
) | ||
.unwrap(); | ||
indexer.progress(|_| { | ||
progress_called = true; | ||
true | ||
}); | ||
indexer.write(&buf).unwrap(); | ||
indexer.commit().unwrap(); | ||
|
||
// Assert that target repo picks it up as valid | ||
let commit_target = repo_target.find_commit(commit_source_id).unwrap(); | ||
assert_eq!(commit_target.id(), commit_source_id); | ||
assert!(progress_called); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having trouble seeing where
progress
is ever initialized. I see it created here, and then a few calls toas_mut_ptr
, but I don't see where it gets initialized beforeas_mut_ptr
is called. Can you help me understand how this works?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, actually,
OdbPackwriter
does the same. I assumed it works becauseMaybeUninit<T>
has the same size and alignment asT
, so the C side can just write to it (i.e. it is initialized after the first time we passas_mut_ptr
over tolibgit2
).I don't know why this would be preferable over deriving
Default
forgit_indexer_progress
, and casting to a raw pointer where needed. If I made this change, would you want me to do it forOdbPackwriter
as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, it looks like OdbPackwriter should also be updated. From what I can tell, the C side is writing to uninitialized memory which is UB to my understanding.