Skip to content

Commit 12a23d2

Browse files
committed
Add bindings to git_indexer
The indexer API is a lower-level interface for storing and indexing pack files, which, unlike `git_odb_write_pack`, allows the ouput to be written to an arbitrary directory. This can be useful when working with unusual validation requirements or non-standard repository layouts.
1 parent 3aa9013 commit 12a23d2

File tree

3 files changed

+178
-1
lines changed

3 files changed

+178
-1
lines changed

Diff for: libgit2-sys/lib.rs

+31
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub const GIT_REFDB_BACKEND_VERSION: c_uint = 1;
2525
pub const GIT_CHERRYPICK_OPTIONS_VERSION: c_uint = 1;
2626
pub const GIT_APPLY_OPTIONS_VERSION: c_uint = 1;
2727
pub const GIT_REVERT_OPTIONS_VERSION: c_uint = 1;
28+
pub const GIT_INDEXER_OPTIONS_VERSION: c_uint = 1;
2829

2930
macro_rules! git_enum {
3031
(pub enum $name:ident { $($variants:tt)* }) => {
@@ -91,6 +92,7 @@ pub enum git_odb_object {}
9192
pub enum git_worktree {}
9293
pub enum git_transaction {}
9394
pub enum git_mailmap {}
95+
pub enum git_indexer {}
9496

9597
#[repr(C)]
9698
pub struct git_revspec {
@@ -354,6 +356,14 @@ pub type git_indexer_progress_cb =
354356
)]
355357
pub type git_transfer_progress = git_indexer_progress;
356358

359+
#[repr(C)]
360+
pub struct git_indexer_options {
361+
pub version: c_uint,
362+
pub progress_cb: git_indexer_progress_cb,
363+
pub progress_cb_payload: *mut c_void,
364+
pub verify: c_uchar,
365+
}
366+
357367
pub type git_remote_ready_cb = Option<extern "C" fn(*mut git_remote, c_int, *mut c_void) -> c_int>;
358368

359369
#[repr(C)]
@@ -3801,6 +3811,27 @@ extern "C" {
38013811
) -> c_int;
38023812
pub fn git_packbuilder_free(pb: *mut git_packbuilder);
38033813

3814+
// indexer
3815+
pub fn git_indexer_new(
3816+
out: *mut *mut git_indexer,
3817+
path: *const c_char,
3818+
mode: c_uint,
3819+
odb: *mut git_odb,
3820+
opts: *mut git_indexer_options,
3821+
) -> c_int;
3822+
pub fn git_indexer_append(
3823+
idx: *mut git_indexer,
3824+
data: *const c_void,
3825+
size: size_t,
3826+
stats: *mut git_indexer_progress,
3827+
) -> c_int;
3828+
pub fn git_indexer_commit(idx: *mut git_indexer, stats: *mut git_indexer_progress) -> c_int;
3829+
pub fn git_indexer_hash(idx: *const git_indexer) -> *const git_oid;
3830+
pub fn git_indexer_name(idx: *const git_indexer) -> *const c_char;
3831+
pub fn git_indexer_free(idx: *mut git_indexer);
3832+
3833+
pub fn git_indexer_options_init(opts: *mut git_indexer_options, version: c_uint) -> c_int;
3834+
38043835
// odb
38053836
pub fn git_repository_odb(out: *mut *mut git_odb, repo: *mut git_repository) -> c_int;
38063837
pub fn git_odb_new(db: *mut *mut git_odb) -> c_int;

Diff for: src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub use crate::message::{
109109
};
110110
pub use crate::note::{Note, Notes};
111111
pub use crate::object::Object;
112-
pub use crate::odb::{Odb, OdbObject, OdbPackwriter, OdbReader, OdbWriter};
112+
pub use crate::odb::{Indexer, Odb, OdbObject, OdbPackwriter, OdbReader, OdbWriter};
113113
pub use crate::oid::Oid;
114114
pub use crate::packbuilder::{PackBuilder, PackBuilderStage};
115115
pub use crate::patch::Patch;

Diff for: src/odb.rs

+146
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use std::ffi::CStr;
12
use std::io;
23
use std::marker;
4+
use std::mem;
35
use std::mem::MaybeUninit;
6+
use std::path::Path;
47
use std::ptr;
58
use std::slice;
69

@@ -10,6 +13,7 @@ use libc::{c_char, c_int, c_uint, c_void, size_t};
1013

1114
use crate::panic;
1215
use crate::util::Binding;
16+
use crate::IntoCString;
1317
use crate::{
1418
raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress,
1519
};
@@ -183,6 +187,41 @@ impl<'repo> Odb<'repo> {
183187
})
184188
}
185189

190+
/// Create a stream for writing a pack file to an arbitrary path
191+
///
192+
/// This [`Odb`] is used to resolve objects if the written pack is "thin", i.e. depends on
193+
/// already-known objects.
194+
///
195+
/// `mode` are the file permissions to use on the output.
196+
pub fn indexer(&self, path: &Path, mode: u32) -> Result<Indexer<'_>, Error> {
197+
let path = path.into_c_string()?;
198+
let mut out = ptr::null_mut();
199+
let progress = MaybeUninit::uninit();
200+
let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
201+
let progress_payload = Box::new(OdbPackwriterCb { cb: None });
202+
let progress_payload_ptr = Box::into_raw(progress_payload);
203+
204+
unsafe {
205+
let mut opts = mem::zeroed();
206+
try_call!(raw::git_indexer_options_init(
207+
&mut opts,
208+
raw::GIT_INDEXER_OPTIONS_VERSION
209+
));
210+
opts.progress_cb = progress_cb;
211+
opts.progress_cb_payload = progress_payload_ptr as *mut c_void;
212+
213+
try_call!(raw::git_indexer_new(
214+
&mut out, path, mode, self.raw, &mut opts
215+
));
216+
}
217+
218+
Ok(Indexer {
219+
raw: out,
220+
progress,
221+
progress_payload_ptr,
222+
})
223+
}
224+
186225
/// Checks if the object database has an object.
187226
pub fn exists(&self, oid: Oid) -> bool {
188227
unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
@@ -519,6 +558,78 @@ impl<'repo> Drop for OdbPackwriter<'repo> {
519558
}
520559
}
521560

561+
/// A stream to write and index a packfile
562+
///
563+
/// This is a lower-level interface than [`OdbPackwriter`] which allows to write the pack data and
564+
/// index to an arbitrary path, but is otherwise equivalent.
565+
pub struct Indexer<'odb> {
566+
raw: *mut raw::git_indexer,
567+
progress: MaybeUninit<raw::git_indexer_progress>,
568+
progress_payload_ptr: *mut OdbPackwriterCb<'odb>,
569+
}
570+
571+
impl<'a> Indexer<'a> {
572+
/// Finalize the pack and index
573+
///
574+
/// Resolves any pending deltas and writes out the index file. The returned string is the
575+
/// hexadecimal checksum of the packfile, which is also used to name the pack and index files
576+
/// (`pack-<checksum>.pack` and `pack-<checksum>.idx` respectively).
577+
pub fn commit(mut self) -> Result<String, Error> {
578+
unsafe {
579+
try_call!(raw::git_indexer_commit(
580+
self.raw,
581+
self.progress.as_mut_ptr()
582+
));
583+
584+
let name = CStr::from_ptr(raw::git_indexer_name(self.raw));
585+
Ok(name.to_str().expect("pack name not utf8").to_owned())
586+
}
587+
}
588+
589+
/// The callback through which progress is monitored. Be aware that this is
590+
/// called inline, so performance may be affected.
591+
pub fn progress<F>(&mut self, cb: F) -> &mut Self
592+
where
593+
F: FnMut(Progress<'_>) -> bool + 'a,
594+
{
595+
let progress_payload =
596+
unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
597+
progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
598+
599+
self
600+
}
601+
}
602+
603+
impl io::Write for Indexer<'_> {
604+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
605+
unsafe {
606+
let ptr = buf.as_ptr() as *mut c_void;
607+
let len = buf.len();
608+
609+
let res = raw::git_indexer_append(self.raw, ptr, len, self.progress.as_mut_ptr());
610+
611+
if res < 0 {
612+
Err(io::Error::new(io::ErrorKind::Other, "Write error"))
613+
} else {
614+
Ok(buf.len())
615+
}
616+
}
617+
}
618+
619+
fn flush(&mut self) -> io::Result<()> {
620+
Ok(())
621+
}
622+
}
623+
624+
impl Drop for Indexer<'_> {
625+
fn drop(&mut self) {
626+
unsafe {
627+
raw::git_indexer_free(self.raw);
628+
drop(Box::from_raw(self.progress_payload_ptr))
629+
}
630+
}
631+
}
632+
522633
pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
523634

524635
struct ForeachCbData<'a> {
@@ -728,4 +839,39 @@ mod tests {
728839
t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
729840
assert!(foo_file.exists());
730841
}
842+
843+
#[test]
844+
fn indexer() {
845+
let (_td, repo_source) = crate::test::repo_init();
846+
let (_td, repo_target) = crate::test::repo_init();
847+
848+
let mut progress_called = false;
849+
850+
// Create an in-memory packfile
851+
let mut builder = t!(repo_source.packbuilder());
852+
let mut buf = Buf::new();
853+
let (commit_source_id, _tree) = crate::test::commit(&repo_source);
854+
t!(builder.insert_object(commit_source_id, None));
855+
t!(builder.write_buf(&mut buf));
856+
857+
// Write it to the standard location in the target repo, but via indexer
858+
let odb = repo_source.odb().unwrap();
859+
let mut indexer = odb
860+
.indexer(
861+
repo_target.path().join("objects").join("pack").as_path(),
862+
0o644,
863+
)
864+
.unwrap();
865+
indexer.progress(|_| {
866+
progress_called = true;
867+
true
868+
});
869+
indexer.write(&buf).unwrap();
870+
indexer.commit().unwrap();
871+
872+
// Assert that target repo picks it up as valid
873+
let commit_target = repo_target.find_commit(commit_source_id).unwrap();
874+
assert_eq!(commit_target.id(), commit_source_id);
875+
assert!(progress_called);
876+
}
731877
}

0 commit comments

Comments
 (0)