Skip to content

Commit f08525f

Browse files
authored
Merge pull request #911 from kim/indexer
Add bindings to git_indexer
2 parents d5320a8 + f36cf67 commit f08525f

File tree

4 files changed

+202
-13
lines changed

4 files changed

+202
-13
lines changed

Diff for: libgit2-sys/lib.rs

+33-1
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 {
@@ -334,7 +336,7 @@ pub struct git_checkout_perfdata {
334336
}
335337

336338
#[repr(C)]
337-
#[derive(Copy, Clone)]
339+
#[derive(Copy, Clone, Default)]
338340
pub struct git_indexer_progress {
339341
pub total_objects: c_uint,
340342
pub indexed_objects: c_uint,
@@ -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)]
@@ -3809,6 +3819,28 @@ extern "C" {
38093819
) -> c_int;
38103820
pub fn git_packbuilder_free(pb: *mut git_packbuilder);
38113821

3822+
// indexer
3823+
pub fn git_indexer_new(
3824+
out: *mut *mut git_indexer,
3825+
path: *const c_char,
3826+
mode: c_uint,
3827+
odb: *mut git_odb,
3828+
opts: *mut git_indexer_options,
3829+
) -> c_int;
3830+
pub fn git_indexer_append(
3831+
idx: *mut git_indexer,
3832+
data: *const c_void,
3833+
size: size_t,
3834+
stats: *mut git_indexer_progress,
3835+
) -> c_int;
3836+
pub fn git_indexer_commit(idx: *mut git_indexer, stats: *mut git_indexer_progress) -> c_int;
3837+
#[deprecated = "use `git_indexer_name` to retrieve the filename"]
3838+
pub fn git_indexer_hash(idx: *const git_indexer) -> *const git_oid;
3839+
pub fn git_indexer_name(idx: *const git_indexer) -> *const c_char;
3840+
pub fn git_indexer_free(idx: *mut git_indexer);
3841+
3842+
pub fn git_indexer_options_init(opts: *mut git_indexer_options, version: c_uint) -> c_int;
3843+
38123844
// odb
38133845
pub fn git_repository_odb(out: *mut *mut git_odb, repo: *mut git_repository) -> c_int;
38143846
pub fn git_odb_new(db: *mut *mut git_odb) -> c_int;

Diff for: src/indexer.rs

+161-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
use std::marker;
1+
use std::ffi::CStr;
2+
use std::path::Path;
3+
use std::{io, marker, mem, ptr};
24

3-
use crate::raw;
5+
use libc::c_void;
6+
7+
use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
48
use crate::util::Binding;
9+
use crate::{raw, Error, IntoCString, Odb};
510

611
/// Struct representing the progress by an in-flight transfer.
712
pub struct Progress<'a> {
@@ -94,3 +99,157 @@ impl<'a> Binding for Progress<'a> {
9499
)]
95100
#[allow(dead_code)]
96101
pub type TransportProgress<'a> = IndexerProgress<'a>;
102+
103+
/// A stream to write and index a packfile
104+
///
105+
/// This is equivalent to [`crate::OdbPackwriter`], but allows to store the pack
106+
/// and index at an arbitrary path. It also does not require access to an object
107+
/// database if, and only if, the pack file is self-contained (i.e. not "thin").
108+
pub struct Indexer<'odb> {
109+
raw: *mut raw::git_indexer,
110+
progress: raw::git_indexer_progress,
111+
progress_payload_ptr: *mut OdbPackwriterCb<'odb>,
112+
}
113+
114+
impl<'a> Indexer<'a> {
115+
/// Create a new indexer
116+
///
117+
/// The [`Odb`] is used to resolve base objects when fixing thin packs. It
118+
/// can be `None` if no thin pack is expected, in which case missing bases
119+
/// will result in an error.
120+
///
121+
/// `mode` is the permissions to use for the output files, use `0` for defaults.
122+
///
123+
/// If `verify` is `false`, the indexer will bypass object connectivity checks.
124+
pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> {
125+
let path = path.into_c_string()?;
126+
127+
let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut);
128+
129+
let mut out = ptr::null_mut();
130+
let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
131+
let progress_payload = Box::new(OdbPackwriterCb { cb: None });
132+
let progress_payload_ptr = Box::into_raw(progress_payload);
133+
134+
unsafe {
135+
let mut opts = mem::zeroed();
136+
try_call!(raw::git_indexer_options_init(
137+
&mut opts,
138+
raw::GIT_INDEXER_OPTIONS_VERSION
139+
));
140+
opts.progress_cb = progress_cb;
141+
opts.progress_cb_payload = progress_payload_ptr as *mut c_void;
142+
opts.verify = verify.into();
143+
144+
try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts));
145+
}
146+
147+
Ok(Self {
148+
raw: out,
149+
progress: Default::default(),
150+
progress_payload_ptr,
151+
})
152+
}
153+
154+
/// Finalize the pack and index
155+
///
156+
/// Resolves any pending deltas and writes out the index file. The returned
157+
/// string is the hexadecimal checksum of the packfile, which is also used
158+
/// to name the pack and index files (`pack-<checksum>.pack` and
159+
/// `pack-<checksum>.idx` respectively).
160+
pub fn commit(mut self) -> Result<String, Error> {
161+
unsafe {
162+
try_call!(raw::git_indexer_commit(self.raw, &mut self.progress));
163+
164+
let name = CStr::from_ptr(raw::git_indexer_name(self.raw));
165+
Ok(name.to_str().expect("pack name not utf8").to_owned())
166+
}
167+
}
168+
169+
/// The callback through which progress is monitored. Be aware that this is
170+
/// called inline, so performance may be affected.
171+
pub fn progress<F>(&mut self, cb: F) -> &mut Self
172+
where
173+
F: FnMut(Progress<'_>) -> bool + 'a,
174+
{
175+
let progress_payload =
176+
unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
177+
progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
178+
179+
self
180+
}
181+
}
182+
183+
impl io::Write for Indexer<'_> {
184+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
185+
unsafe {
186+
let ptr = buf.as_ptr() as *mut c_void;
187+
let len = buf.len();
188+
189+
let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress);
190+
if res < 0 {
191+
Err(io::Error::new(
192+
io::ErrorKind::Other,
193+
Error::last_error(res).unwrap(),
194+
))
195+
} else {
196+
Ok(buf.len())
197+
}
198+
}
199+
}
200+
201+
fn flush(&mut self) -> io::Result<()> {
202+
Ok(())
203+
}
204+
}
205+
206+
impl Drop for Indexer<'_> {
207+
fn drop(&mut self) {
208+
unsafe {
209+
raw::git_indexer_free(self.raw);
210+
drop(Box::from_raw(self.progress_payload_ptr))
211+
}
212+
}
213+
}
214+
215+
#[cfg(test)]
216+
mod tests {
217+
use crate::{Buf, Indexer};
218+
use std::io::prelude::*;
219+
220+
#[test]
221+
fn indexer() {
222+
let (_td, repo_source) = crate::test::repo_init();
223+
let (_td, repo_target) = crate::test::repo_init();
224+
225+
let mut progress_called = false;
226+
227+
// Create an in-memory packfile
228+
let mut builder = t!(repo_source.packbuilder());
229+
let mut buf = Buf::new();
230+
let (commit_source_id, _tree) = crate::test::commit(&repo_source);
231+
t!(builder.insert_object(commit_source_id, None));
232+
t!(builder.write_buf(&mut buf));
233+
234+
// Write it to the standard location in the target repo, but via indexer
235+
let odb = repo_source.odb().unwrap();
236+
let mut indexer = Indexer::new(
237+
Some(&odb),
238+
repo_target.path().join("objects").join("pack").as_path(),
239+
0o644,
240+
true,
241+
)
242+
.unwrap();
243+
indexer.progress(|_| {
244+
progress_called = true;
245+
true
246+
});
247+
indexer.write(&buf).unwrap();
248+
indexer.commit().unwrap();
249+
250+
// Assert that target repo picks it up as valid
251+
let commit_target = repo_target.find_commit(commit_source_id).unwrap();
252+
assert_eq!(commit_target.id(), commit_source_id);
253+
assert!(progress_called);
254+
}
255+
}

Diff for: src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub use crate::error::Error;
9898
pub use crate::index::{
9999
Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath,
100100
};
101-
pub use crate::indexer::{IndexerProgress, Progress};
101+
pub use crate::indexer::{Indexer, IndexerProgress, Progress};
102102
pub use crate::mailmap::Mailmap;
103103
pub use crate::mempack::Mempack;
104104
pub use crate::merge::{AnnotatedCommit, MergeOptions};

Diff for: src/odb.rs

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::io;
22
use std::marker;
3-
use std::mem::MaybeUninit;
43
use std::ptr;
54
use std::slice;
65

@@ -162,7 +161,6 @@ impl<'repo> Odb<'repo> {
162161
/// Create stream for writing a pack file to the ODB
163162
pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> {
164163
let mut out = ptr::null_mut();
165-
let progress = MaybeUninit::uninit();
166164
let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
167165
let progress_payload = Box::new(OdbPackwriterCb { cb: None });
168166
let progress_payload_ptr = Box::into_raw(progress_payload);
@@ -178,7 +176,7 @@ impl<'repo> Odb<'repo> {
178176

179177
Ok(OdbPackwriter {
180178
raw: out,
181-
progress,
179+
progress: Default::default(),
182180
progress_payload_ptr,
183181
})
184182
}
@@ -438,14 +436,14 @@ impl<'repo> io::Write for OdbWriter<'repo> {
438436
}
439437
}
440438

441-
struct OdbPackwriterCb<'repo> {
442-
cb: Option<Box<IndexerProgress<'repo>>>,
439+
pub(crate) struct OdbPackwriterCb<'repo> {
440+
pub(crate) cb: Option<Box<IndexerProgress<'repo>>>,
443441
}
444442

445443
/// A stream to write a packfile to the ODB
446444
pub struct OdbPackwriter<'repo> {
447445
raw: *mut raw::git_odb_writepack,
448-
progress: MaybeUninit<raw::git_indexer_progress>,
446+
progress: raw::git_indexer_progress,
449447
progress_payload_ptr: *mut OdbPackwriterCb<'repo>,
450448
}
451449

@@ -455,7 +453,7 @@ impl<'repo> OdbPackwriter<'repo> {
455453
unsafe {
456454
let writepack = &*self.raw;
457455
let res = match writepack.commit {
458-
Some(commit) => commit(self.raw, self.progress.as_mut_ptr()),
456+
Some(commit) => commit(self.raw, &mut self.progress),
459457
None => -1,
460458
};
461459

@@ -489,7 +487,7 @@ impl<'repo> io::Write for OdbPackwriter<'repo> {
489487

490488
let writepack = &*self.raw;
491489
let res = match writepack.append {
492-
Some(append) => append(self.raw, ptr, len, self.progress.as_mut_ptr()),
490+
Some(append) => append(self.raw, ptr, len, &mut self.progress),
493491
None => -1,
494492
};
495493

@@ -542,7 +540,7 @@ extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int
542540
.unwrap_or(1)
543541
}
544542

545-
extern "C" fn write_pack_progress_cb(
543+
pub(crate) extern "C" fn write_pack_progress_cb(
546544
stats: *const raw::git_indexer_progress,
547545
payload: *mut c_void,
548546
) -> c_int {

0 commit comments

Comments
 (0)