1use std::ffi::CStr;
2use std::path::Path;
3use std::{io, marker, mem, ptr};
4
5use libc::c_void;
6
7use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
8use crate::util::Binding;
9use crate::{raw, Error, IntoCString, Odb};
10
11pub struct Progress<'a> {
13 pub(crate) raw: ProgressState,
14 pub(crate) _marker: marker::PhantomData<&'a raw::git_indexer_progress>,
15}
16
17pub(crate) enum ProgressState {
18 Borrowed(*const raw::git_indexer_progress),
19 Owned(raw::git_indexer_progress),
20}
21
22pub type IndexerProgress<'a> = dyn FnMut(Progress<'_>) -> bool + 'a;
31
32impl<'a> Progress<'a> {
33 pub fn total_objects(&self) -> usize {
35 unsafe { (*self.raw()).total_objects as usize }
36 }
37 pub fn indexed_objects(&self) -> usize {
39 unsafe { (*self.raw()).indexed_objects as usize }
40 }
41 pub fn received_objects(&self) -> usize {
43 unsafe { (*self.raw()).received_objects as usize }
44 }
45 pub fn local_objects(&self) -> usize {
48 unsafe { (*self.raw()).local_objects as usize }
49 }
50 pub fn total_deltas(&self) -> usize {
52 unsafe { (*self.raw()).total_deltas as usize }
53 }
54 pub fn indexed_deltas(&self) -> usize {
56 unsafe { (*self.raw()).indexed_deltas as usize }
57 }
58 pub fn received_bytes(&self) -> usize {
60 unsafe { (*self.raw()).received_bytes as usize }
61 }
62
63 pub fn to_owned(&self) -> Progress<'static> {
65 Progress {
66 raw: ProgressState::Owned(unsafe { *self.raw() }),
67 _marker: marker::PhantomData,
68 }
69 }
70}
71
72impl<'a> Binding for Progress<'a> {
73 type Raw = *const raw::git_indexer_progress;
74 unsafe fn from_raw(raw: *const raw::git_indexer_progress) -> Progress<'a> {
75 Progress {
76 raw: ProgressState::Borrowed(raw),
77 _marker: marker::PhantomData,
78 }
79 }
80
81 fn raw(&self) -> *const raw::git_indexer_progress {
82 match self.raw {
83 ProgressState::Borrowed(raw) => raw,
84 ProgressState::Owned(ref raw) => raw as *const _,
85 }
86 }
87}
88
89#[deprecated(
97 since = "0.11.0",
98 note = "renamed to `IndexerProgress` to match upstream"
99)]
100#[allow(dead_code)]
101pub type TransportProgress<'a> = IndexerProgress<'a>;
102
103pub struct Indexer<'odb> {
109 raw: *mut raw::git_indexer,
110 progress: raw::git_indexer_progress,
111 progress_payload_ptr: *mut OdbPackwriterCb<'odb>,
112}
113
114impl<'a> Indexer<'a> {
115 pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> {
127 crate::init();
128 let path = path.into_c_string()?;
129
130 let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut);
131
132 let mut out = ptr::null_mut();
133 let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
134 let progress_payload = Box::new(OdbPackwriterCb { cb: None });
135 let progress_payload_ptr = Box::into_raw(progress_payload);
136
137 unsafe {
138 let mut opts = mem::zeroed();
139 try_call!(raw::git_indexer_options_init(
140 &mut opts,
141 raw::GIT_INDEXER_OPTIONS_VERSION
142 ));
143 opts.progress_cb = progress_cb;
144 opts.progress_cb_payload = progress_payload_ptr as *mut c_void;
145 opts.verify = verify.into();
146
147 try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts));
148 }
149
150 Ok(Self {
151 raw: out,
152 progress: Default::default(),
153 progress_payload_ptr,
154 })
155 }
156
157 pub fn commit(mut self) -> Result<String, Error> {
164 unsafe {
165 try_call!(raw::git_indexer_commit(self.raw, &mut self.progress));
166
167 let name = CStr::from_ptr(raw::git_indexer_name(self.raw));
168 Ok(name.to_str().expect("pack name not utf8").to_owned())
169 }
170 }
171
172 pub fn progress<F>(&mut self, cb: F) -> &mut Self
175 where
176 F: FnMut(Progress<'_>) -> bool + 'a,
177 {
178 let progress_payload =
179 unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
180 progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
181
182 self
183 }
184}
185
186impl io::Write for Indexer<'_> {
187 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
188 unsafe {
189 let ptr = buf.as_ptr() as *mut c_void;
190 let len = buf.len();
191
192 let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress);
193 if res < 0 {
194 Err(io::Error::new(io::ErrorKind::Other, Error::last_error(res)))
195 } else {
196 Ok(buf.len())
197 }
198 }
199 }
200
201 fn flush(&mut self) -> io::Result<()> {
202 Ok(())
203 }
204}
205
206impl 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)]
216mod 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 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 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 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}