1use libc::{c_int, c_uint, c_void, size_t};
2use std::marker;
3use std::path::Path;
4use std::ptr;
5use std::slice;
6use std::str;
7
8use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
9use crate::util::Binding;
10use crate::IntoCString;
11use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk};
12
13#[derive(PartialEq, Eq, Clone, Debug, Copy)]
14pub enum PackBuilderStage {
16 AddingObjects,
18 Deltafication,
20}
21
22pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a;
23pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a;
24
25pub struct PackBuilder<'repo> {
27 raw: *mut raw::git_packbuilder,
28 _progress: Option<Box<Box<ProgressCb<'repo>>>>,
29 _marker: marker::PhantomData<&'repo Repository>,
30}
31
32impl<'repo> PackBuilder<'repo> {
33 pub fn insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
36 let name = crate::opt_cstr(name)?;
37 unsafe {
38 try_call!(raw::git_packbuilder_insert(self.raw, id.raw(), name));
39 }
40 Ok(())
41 }
42
43 pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> {
46 unsafe {
47 try_call!(raw::git_packbuilder_insert_tree(self.raw, id.raw()));
48 }
49 Ok(())
50 }
51
52 pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> {
55 unsafe {
56 try_call!(raw::git_packbuilder_insert_commit(self.raw, id.raw()));
57 }
58 Ok(())
59 }
60
61 pub fn insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error> {
64 unsafe {
65 try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw()));
66 }
67 Ok(())
68 }
69
70 pub fn insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
73 let name = crate::opt_cstr(name)?;
74 unsafe {
75 try_call!(raw::git_packbuilder_insert_recur(self.raw, id.raw(), name));
76 }
77 Ok(())
78 }
79
80 pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> {
84 unsafe {
85 try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw));
86 }
87 Ok(())
88 }
89
90 pub fn write(&mut self, path: &Path, mode: u32) -> Result<(), Error> {
93 let path = path.into_c_string()?;
94 let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
95 let progress_payload = Box::new(OdbPackwriterCb { cb: None });
96 let progress_payload_ptr = Box::into_raw(progress_payload);
97
98 unsafe {
99 try_call!(raw::git_packbuilder_write(
100 self.raw,
101 path,
102 mode,
103 progress_cb,
104 progress_payload_ptr as *mut _
105 ));
106 }
107 Ok(())
108 }
109
110 pub fn foreach<F>(&mut self, mut cb: F) -> Result<(), Error>
112 where
113 F: FnMut(&[u8]) -> bool,
114 {
115 let mut cb = &mut cb as &mut ForEachCb<'_>;
116 let ptr = &mut cb as *mut _;
117 let foreach: raw::git_packbuilder_foreach_cb = Some(foreach_c);
118 unsafe {
119 try_call!(raw::git_packbuilder_foreach(
120 self.raw,
121 foreach,
122 ptr as *mut _
123 ));
124 }
125 Ok(())
126 }
127
128 pub fn set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error>
136 where
137 F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo,
138 {
139 let mut progress = Box::new(Box::new(progress) as Box<ProgressCb<'_>>);
140 let ptr = &mut *progress as *mut _;
141 let progress_c: raw::git_packbuilder_progress = Some(progress_c);
142 unsafe {
143 try_call!(raw::git_packbuilder_set_callbacks(
144 self.raw,
145 progress_c,
146 ptr as *mut _
147 ));
148 }
149 self._progress = Some(progress);
150 Ok(())
151 }
152
153 pub fn unset_progress_callback(&mut self) -> Result<(), Error> {
156 unsafe {
157 try_call!(raw::git_packbuilder_set_callbacks(
158 self.raw,
159 None,
160 ptr::null_mut()
161 ));
162 self._progress = None;
163 }
164 Ok(())
165 }
166
167 pub fn set_threads(&mut self, threads: u32) -> u32 {
171 unsafe { raw::git_packbuilder_set_threads(self.raw, threads) }
172 }
173
174 pub fn object_count(&self) -> usize {
176 unsafe { raw::git_packbuilder_object_count(self.raw) }
177 }
178
179 pub fn written(&self) -> usize {
181 unsafe { raw::git_packbuilder_written(self.raw) }
182 }
183
184 #[deprecated = "use `name()` to retrieve the filename"]
188 #[allow(deprecated)]
189 pub fn hash(&self) -> Option<Oid> {
190 if self.object_count() == 0 {
191 unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) }
192 } else {
193 None
194 }
195 }
196
197 pub fn name(&self) -> Option<&str> {
205 self.name_bytes().and_then(|s| str::from_utf8(s).ok())
206 }
207
208 pub fn name_bytes(&self) -> Option<&[u8]> {
213 unsafe { crate::opt_bytes(self, raw::git_packbuilder_name(self.raw)) }
214 }
215}
216
217impl<'repo> Binding for PackBuilder<'repo> {
218 type Raw = *mut raw::git_packbuilder;
219 unsafe fn from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo> {
220 PackBuilder {
221 raw: ptr,
222 _progress: None,
223 _marker: marker::PhantomData,
224 }
225 }
226 fn raw(&self) -> *mut raw::git_packbuilder {
227 self.raw
228 }
229}
230
231impl<'repo> Drop for PackBuilder<'repo> {
232 fn drop(&mut self) {
233 unsafe {
234 raw::git_packbuilder_set_callbacks(self.raw, None, ptr::null_mut());
235 raw::git_packbuilder_free(self.raw);
236 }
237 }
238}
239
240impl Binding for PackBuilderStage {
241 type Raw = raw::git_packbuilder_stage_t;
242 unsafe fn from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage {
243 match raw {
244 raw::GIT_PACKBUILDER_ADDING_OBJECTS => PackBuilderStage::AddingObjects,
245 raw::GIT_PACKBUILDER_DELTAFICATION => PackBuilderStage::Deltafication,
246 _ => panic!("Unknown git diff binary kind"),
247 }
248 }
249 fn raw(&self) -> raw::git_packbuilder_stage_t {
250 match *self {
251 PackBuilderStage::AddingObjects => raw::GIT_PACKBUILDER_ADDING_OBJECTS,
252 PackBuilderStage::Deltafication => raw::GIT_PACKBUILDER_DELTAFICATION,
253 }
254 }
255}
256
257extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int {
258 unsafe {
259 let buf = slice::from_raw_parts(buf as *const u8, size as usize);
260
261 let r = panic::wrap(|| {
262 let data = data as *mut &mut ForEachCb<'_>;
263 (*data)(buf)
264 });
265 if r == Some(true) {
266 0
267 } else {
268 -1
269 }
270 }
271}
272
273extern "C" fn progress_c(
274 stage: raw::git_packbuilder_stage_t,
275 current: c_uint,
276 total: c_uint,
277 data: *mut c_void,
278) -> c_int {
279 unsafe {
280 let stage = Binding::from_raw(stage);
281
282 let r = panic::wrap(|| {
283 let data = data as *mut Box<ProgressCb<'_>>;
284 (*data)(stage, current, total)
285 });
286 if r == Some(true) {
287 0
288 } else {
289 -1
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use crate::{Buf, Oid};
297
298 const EMPTY_PACKFILE_OID: &str = "029d08823bd8a8eab510ad6ac75c823cfd3ed31e";
300
301 fn pack_header(len: u8) -> Vec<u8> {
302 [].iter()
303 .chain(b"PACK") .chain(&[0, 0, 0, 2]) .chain(&[0, 0, 0, len]) .cloned()
307 .collect::<Vec<u8>>()
308 }
309
310 fn empty_pack_header() -> Vec<u8> {
311 pack_header(0)
312 .iter()
313 .chain(&[
314 0x02, 0x9d, 0x08, 0x82, 0x3b, 0xd8, 0xa8, 0xea, 0xb5, 0x10, 0xad, 0x6a, 0xc7, 0x5c, 0x82, 0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
318 ]) .cloned()
320 .collect::<Vec<u8>>()
321 }
322
323 #[test]
324 fn smoke() {
325 let (_td, repo) = crate::test::repo_init();
326 let _builder = t!(repo.packbuilder());
327 }
328
329 #[test]
330 fn smoke_write_buf() {
331 let (_td, repo) = crate::test::repo_init();
332 let mut builder = t!(repo.packbuilder());
333 let mut buf = Buf::new();
334 t!(builder.write_buf(&mut buf));
335 #[allow(deprecated)]
336 {
337 assert!(builder.hash().unwrap().is_zero());
338 }
339 assert!(builder.name().is_none());
340 assert_eq!(&*buf, &*empty_pack_header());
341 }
342
343 #[test]
344 fn smoke_write() {
345 let (_td, repo) = crate::test::repo_init();
346 let mut builder = t!(repo.packbuilder());
347 t!(builder.write(repo.path(), 0));
348 #[allow(deprecated)]
349 {
350 assert!(builder.hash().unwrap() == Oid::from_str(EMPTY_PACKFILE_OID).unwrap());
351 }
352 assert!(builder.name().unwrap() == EMPTY_PACKFILE_OID);
353 }
354
355 #[test]
356 fn smoke_foreach() {
357 let (_td, repo) = crate::test::repo_init();
358 let mut builder = t!(repo.packbuilder());
359 let mut buf = Vec::<u8>::new();
360 t!(builder.foreach(|bytes| {
361 buf.extend(bytes);
362 true
363 }));
364 assert_eq!(&*buf, &*empty_pack_header());
365 }
366
367 #[test]
368 fn insert_write_buf() {
369 let (_td, repo) = crate::test::repo_init();
370 let mut builder = t!(repo.packbuilder());
371 let mut buf = Buf::new();
372 let (commit, _tree) = crate::test::commit(&repo);
373 t!(builder.insert_object(commit, None));
374 assert_eq!(builder.object_count(), 1);
375 t!(builder.write_buf(&mut buf));
376 assert_eq!(&buf[0..12], &*pack_header(1));
378 }
379
380 #[test]
381 fn insert_tree_write_buf() {
382 let (_td, repo) = crate::test::repo_init();
383 let mut builder = t!(repo.packbuilder());
384 let mut buf = Buf::new();
385 let (_commit, tree) = crate::test::commit(&repo);
386 t!(builder.insert_tree(tree));
388 assert_eq!(builder.object_count(), 2);
389 t!(builder.write_buf(&mut buf));
390 assert_eq!(&buf[0..12], &*pack_header(2));
392 }
393
394 #[test]
395 fn insert_commit_write_buf() {
396 let (_td, repo) = crate::test::repo_init();
397 let mut builder = t!(repo.packbuilder());
398 let mut buf = Buf::new();
399 let (commit, _tree) = crate::test::commit(&repo);
400 t!(builder.insert_commit(commit));
402 assert_eq!(builder.object_count(), 3);
403 t!(builder.write_buf(&mut buf));
404 assert_eq!(&buf[0..12], &*pack_header(3));
406 }
407
408 #[test]
409 fn insert_write() {
410 let (_td, repo) = crate::test::repo_init();
411 let mut builder = t!(repo.packbuilder());
412 let (commit, _tree) = crate::test::commit(&repo);
413 t!(builder.insert_object(commit, None));
414 assert_eq!(builder.object_count(), 1);
415 t!(builder.write(repo.path(), 0));
416 t!(repo.find_commit(commit));
417 }
418
419 #[test]
420 fn insert_tree_write() {
421 let (_td, repo) = crate::test::repo_init();
422 let mut builder = t!(repo.packbuilder());
423 let (_commit, tree) = crate::test::commit(&repo);
424 t!(builder.insert_tree(tree));
426 assert_eq!(builder.object_count(), 2);
427 t!(builder.write(repo.path(), 0));
428 t!(repo.find_tree(tree));
429 }
430
431 #[test]
432 fn insert_commit_write() {
433 let (_td, repo) = crate::test::repo_init();
434 let mut builder = t!(repo.packbuilder());
435 let (commit, _tree) = crate::test::commit(&repo);
436 t!(builder.insert_commit(commit));
438 assert_eq!(builder.object_count(), 3);
439 t!(builder.write(repo.path(), 0));
440 t!(repo.find_commit(commit));
441 }
442
443 #[test]
444 fn progress_callback() {
445 let mut progress_called = false;
446 {
447 let (_td, repo) = crate::test::repo_init();
448 let mut builder = t!(repo.packbuilder());
449 let (commit, _tree) = crate::test::commit(&repo);
450 t!(builder.set_progress_callback(|_, _, _| {
451 progress_called = true;
452 true
453 }));
454 t!(builder.insert_commit(commit));
455 t!(builder.write_buf(&mut Buf::new()));
456 }
457 assert_eq!(progress_called, true);
458 }
459
460 #[test]
461 fn clear_progress_callback() {
462 let mut progress_called = false;
463 {
464 let (_td, repo) = crate::test::repo_init();
465 let mut builder = t!(repo.packbuilder());
466 let (commit, _tree) = crate::test::commit(&repo);
467 t!(builder.set_progress_callback(|_, _, _| {
468 progress_called = true;
469 true
470 }));
471 t!(builder.unset_progress_callback());
472 t!(builder.insert_commit(commit));
473 t!(builder.write_buf(&mut Buf::new()));
474 }
475 assert_eq!(progress_called, false);
476 }
477
478 #[test]
479 fn progress_callback_with_write() {
480 let mut progress_called = false;
481 {
482 let (_td, repo) = crate::test::repo_init();
483 let mut builder = t!(repo.packbuilder());
484 let (commit, _tree) = crate::test::commit(&repo);
485 t!(builder.set_progress_callback(|_, _, _| {
486 progress_called = true;
487 true
488 }));
489 t!(builder.insert_commit(commit));
490 t!(builder.write(repo.path(), 0));
491 }
492 assert_eq!(progress_called, true);
493 }
494
495 #[test]
496 fn set_threads() {
497 let (_td, repo) = crate::test::repo_init();
498 let mut builder = t!(repo.packbuilder());
499 let used = builder.set_threads(4);
500 assert!(used == 1 || used == 4);
502 }
503}