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> {
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 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 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
183impl 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(io::ErrorKind::Other, Error::last_error(res)))
192 } else {
193 Ok(buf.len())
194 }
195 }
196 }
197
198 fn flush(&mut self) -> io::Result<()> {
199 Ok(())
200 }
201}
202
203impl Drop for Indexer<'_> {
204 fn drop(&mut self) {
205 unsafe {
206 raw::git_indexer_free(self.raw);
207 drop(Box::from_raw(self.progress_payload_ptr))
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use crate::{Buf, Indexer};
215 use std::io::prelude::*;
216
217 #[test]
218 fn indexer() {
219 let (_td, repo_source) = crate::test::repo_init();
220 let (_td, repo_target) = crate::test::repo_init();
221
222 let mut progress_called = false;
223
224 let mut builder = t!(repo_source.packbuilder());
226 let mut buf = Buf::new();
227 let (commit_source_id, _tree) = crate::test::commit(&repo_source);
228 t!(builder.insert_object(commit_source_id, None));
229 t!(builder.write_buf(&mut buf));
230
231 let odb = repo_source.odb().unwrap();
233 let mut indexer = Indexer::new(
234 Some(&odb),
235 repo_target.path().join("objects").join("pack").as_path(),
236 0o644,
237 true,
238 )
239 .unwrap();
240 indexer.progress(|_| {
241 progress_called = true;
242 true
243 });
244 indexer.write(&buf).unwrap();
245 indexer.commit().unwrap();
246
247 let commit_target = repo_target.find_commit(commit_source_id).unwrap();
249 assert_eq!(commit_target.id(), commit_source_id);
250 assert!(progress_called);
251 }
252}