1 use libc::{c_int, c_uint, c_void, size_t};
2 use std::marker;
3 use std::ptr;
4 use std::slice;
5 
6 use crate::util::Binding;
7 use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk};
8 
9 #[derive(PartialEq, Eq, Clone, Debug, Copy)]
10 /// Stages that are reported by the `PackBuilder` progress callback.
11 pub enum PackBuilderStage {
12     /// Adding objects to the pack
13     AddingObjects,
14     /// Deltafication of the pack
15     Deltafication,
16 }
17 
18 pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a;
19 pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a;
20 
21 /// A builder for creating a packfile
22 pub struct PackBuilder<'repo> {
23     raw: *mut raw::git_packbuilder,
24     _progress: Option<Box<Box<ProgressCb<'repo>>>>,
25     _marker: marker::PhantomData<&'repo Repository>,
26 }
27 
28 impl<'repo> PackBuilder<'repo> {
29     /// Insert a single object. For an optimal pack it's mandatory to insert
30     /// objects in recency order, commits followed by trees and blobs.
insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error>31     pub fn insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
32         let name = crate::opt_cstr(name)?;
33         unsafe {
34             try_call!(raw::git_packbuilder_insert(self.raw, id.raw(), name));
35         }
36         Ok(())
37     }
38 
39     /// Insert a root tree object. This will add the tree as well as all
40     /// referenced trees and blobs.
insert_tree(&mut self, id: Oid) -> Result<(), Error>41     pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> {
42         unsafe {
43             try_call!(raw::git_packbuilder_insert_tree(self.raw, id.raw()));
44         }
45         Ok(())
46     }
47 
48     /// Insert a commit object. This will add a commit as well as the completed
49     /// referenced tree.
insert_commit(&mut self, id: Oid) -> Result<(), Error>50     pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> {
51         unsafe {
52             try_call!(raw::git_packbuilder_insert_commit(self.raw, id.raw()));
53         }
54         Ok(())
55     }
56 
57     /// Insert objects as given by the walk. Those commits and all objects they
58     /// reference will be inserted into the packbuilder.
insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error>59     pub fn insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error> {
60         unsafe {
61             try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw()));
62         }
63         Ok(())
64     }
65 
66     /// Recursively insert an object and its referenced objects. Insert the
67     /// object as well as any object it references.
insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error>68     pub fn insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
69         let name = crate::opt_cstr(name)?;
70         unsafe {
71             try_call!(raw::git_packbuilder_insert_recur(self.raw, id.raw(), name));
72         }
73         Ok(())
74     }
75 
76     /// Write the contents of the packfile to an in-memory buffer. The contents
77     /// of the buffer will become a valid packfile, even though there will be
78     /// no attached index.
write_buf(&mut self, buf: &mut Buf) -> Result<(), Error>79     pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> {
80         unsafe {
81             try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw));
82         }
83         Ok(())
84     }
85 
86     /// Create the new pack and pass each object to the callback.
foreach<F>(&mut self, mut cb: F) -> Result<(), Error> where F: FnMut(&[u8]) -> bool,87     pub fn foreach<F>(&mut self, mut cb: F) -> Result<(), Error>
88     where
89         F: FnMut(&[u8]) -> bool,
90     {
91         let mut cb = &mut cb as &mut ForEachCb<'_>;
92         let ptr = &mut cb as *mut _;
93         let foreach: raw::git_packbuilder_foreach_cb = Some(foreach_c);
94         unsafe {
95             try_call!(raw::git_packbuilder_foreach(
96                 self.raw,
97                 foreach,
98                 ptr as *mut _
99             ));
100         }
101         Ok(())
102     }
103 
104     /// `progress` will be called with progress information during pack
105     /// building. Be aware that this is called inline with pack building
106     /// operations, so performance may be affected.
107     ///
108     /// There can only be one progress callback attached, this will replace any
109     /// existing one. See `unset_progress_callback` to remove the current
110     /// progress callback without attaching a new one.
set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error> where F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo,111     pub fn set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error>
112     where
113         F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo,
114     {
115         let mut progress = Box::new(Box::new(progress) as Box<ProgressCb<'_>>);
116         let ptr = &mut *progress as *mut _;
117         let progress_c: raw::git_packbuilder_progress = Some(progress_c);
118         unsafe {
119             try_call!(raw::git_packbuilder_set_callbacks(
120                 self.raw,
121                 progress_c,
122                 ptr as *mut _
123             ));
124         }
125         self._progress = Some(progress);
126         Ok(())
127     }
128 
129     /// Remove the current progress callback.  See `set_progress_callback` to
130     /// set the progress callback.
unset_progress_callback(&mut self) -> Result<(), Error>131     pub fn unset_progress_callback(&mut self) -> Result<(), Error> {
132         unsafe {
133             try_call!(raw::git_packbuilder_set_callbacks(
134                 self.raw,
135                 None,
136                 ptr::null_mut()
137             ));
138             self._progress = None;
139         }
140         Ok(())
141     }
142 
143     /// Set the number of threads to be used.
144     ///
145     /// Returns the number of threads to be used.
set_threads(&mut self, threads: u32) -> u32146     pub fn set_threads(&mut self, threads: u32) -> u32 {
147         unsafe { raw::git_packbuilder_set_threads(self.raw, threads) }
148     }
149 
150     /// Get the total number of objects the packbuilder will write out.
object_count(&self) -> usize151     pub fn object_count(&self) -> usize {
152         unsafe { raw::git_packbuilder_object_count(self.raw) }
153     }
154 
155     /// Get the number of objects the packbuilder has already written out.
written(&self) -> usize156     pub fn written(&self) -> usize {
157         unsafe { raw::git_packbuilder_written(self.raw) }
158     }
159 
160     /// Get the packfile's hash. A packfile's name is derived from the sorted
161     /// hashing of all object names. This is only correct after the packfile
162     /// has been written.
hash(&self) -> Option<Oid>163     pub fn hash(&self) -> Option<Oid> {
164         if self.object_count() == 0 {
165             unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) }
166         } else {
167             None
168         }
169     }
170 }
171 
172 impl<'repo> Binding for PackBuilder<'repo> {
173     type Raw = *mut raw::git_packbuilder;
from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo>174     unsafe fn from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo> {
175         PackBuilder {
176             raw: ptr,
177             _progress: None,
178             _marker: marker::PhantomData,
179         }
180     }
raw(&self) -> *mut raw::git_packbuilder181     fn raw(&self) -> *mut raw::git_packbuilder {
182         self.raw
183     }
184 }
185 
186 impl<'repo> Drop for PackBuilder<'repo> {
drop(&mut self)187     fn drop(&mut self) {
188         unsafe {
189             raw::git_packbuilder_set_callbacks(self.raw, None, ptr::null_mut());
190             raw::git_packbuilder_free(self.raw);
191         }
192     }
193 }
194 
195 impl Binding for PackBuilderStage {
196     type Raw = raw::git_packbuilder_stage_t;
from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage197     unsafe fn from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage {
198         match raw {
199             raw::GIT_PACKBUILDER_ADDING_OBJECTS => PackBuilderStage::AddingObjects,
200             raw::GIT_PACKBUILDER_DELTAFICATION => PackBuilderStage::Deltafication,
201             _ => panic!("Unknown git diff binary kind"),
202         }
203     }
raw(&self) -> raw::git_packbuilder_stage_t204     fn raw(&self) -> raw::git_packbuilder_stage_t {
205         match *self {
206             PackBuilderStage::AddingObjects => raw::GIT_PACKBUILDER_ADDING_OBJECTS,
207             PackBuilderStage::Deltafication => raw::GIT_PACKBUILDER_DELTAFICATION,
208         }
209     }
210 }
211 
foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int212 extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int {
213     unsafe {
214         let buf = slice::from_raw_parts(buf as *const u8, size as usize);
215 
216         let r = panic::wrap(|| {
217             let data = data as *mut &mut ForEachCb<'_>;
218             (*data)(buf)
219         });
220         if r == Some(true) {
221             0
222         } else {
223             -1
224         }
225     }
226 }
227 
progress_c( stage: raw::git_packbuilder_stage_t, current: c_uint, total: c_uint, data: *mut c_void, ) -> c_int228 extern "C" fn progress_c(
229     stage: raw::git_packbuilder_stage_t,
230     current: c_uint,
231     total: c_uint,
232     data: *mut c_void,
233 ) -> c_int {
234     unsafe {
235         let stage = Binding::from_raw(stage);
236 
237         let r = panic::wrap(|| {
238             let data = data as *mut Box<ProgressCb<'_>>;
239             (*data)(stage, current, total)
240         });
241         if r == Some(true) {
242             0
243         } else {
244             -1
245         }
246     }
247 }
248 
249 #[cfg(test)]
250 mod tests {
251     use crate::Buf;
252 
pack_header(len: u8) -> Vec<u8>253     fn pack_header(len: u8) -> Vec<u8> {
254         [].iter()
255             .chain(b"PACK") // signature
256             .chain(&[0, 0, 0, 2]) // version number
257             .chain(&[0, 0, 0, len]) // number of objects
258             .cloned()
259             .collect::<Vec<u8>>()
260     }
261 
empty_pack_header() -> Vec<u8>262     fn empty_pack_header() -> Vec<u8> {
263         pack_header(0)
264             .iter()
265             .chain(&[
266                 0x02, 0x9d, 0x08, 0x82, 0x3b, // ^
267                 0xd8, 0xa8, 0xea, 0xb5, 0x10, // | SHA-1 of the zero
268                 0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header
269                 0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
270             ]) // v
271             .cloned()
272             .collect::<Vec<u8>>()
273     }
274 
275     #[test]
smoke()276     fn smoke() {
277         let (_td, repo) = crate::test::repo_init();
278         let _builder = t!(repo.packbuilder());
279     }
280 
281     #[test]
smoke_write_buf()282     fn smoke_write_buf() {
283         let (_td, repo) = crate::test::repo_init();
284         let mut builder = t!(repo.packbuilder());
285         let mut buf = Buf::new();
286         t!(builder.write_buf(&mut buf));
287         assert!(builder.hash().unwrap().is_zero());
288         assert_eq!(&*buf, &*empty_pack_header());
289     }
290 
291     #[test]
smoke_foreach()292     fn smoke_foreach() {
293         let (_td, repo) = crate::test::repo_init();
294         let mut builder = t!(repo.packbuilder());
295         let mut buf = Vec::<u8>::new();
296         t!(builder.foreach(|bytes| {
297             buf.extend(bytes);
298             true
299         }));
300         assert_eq!(&*buf, &*empty_pack_header());
301     }
302 
303     #[test]
insert_write_buf()304     fn insert_write_buf() {
305         let (_td, repo) = crate::test::repo_init();
306         let mut builder = t!(repo.packbuilder());
307         let mut buf = Buf::new();
308         let (commit, _tree) = crate::test::commit(&repo);
309         t!(builder.insert_object(commit, None));
310         assert_eq!(builder.object_count(), 1);
311         t!(builder.write_buf(&mut buf));
312         // Just check that the correct number of objects are written
313         assert_eq!(&buf[0..12], &*pack_header(1));
314     }
315 
316     #[test]
insert_tree_write_buf()317     fn insert_tree_write_buf() {
318         let (_td, repo) = crate::test::repo_init();
319         let mut builder = t!(repo.packbuilder());
320         let mut buf = Buf::new();
321         let (_commit, tree) = crate::test::commit(&repo);
322         // will insert the tree itself and the blob, 2 objects
323         t!(builder.insert_tree(tree));
324         assert_eq!(builder.object_count(), 2);
325         t!(builder.write_buf(&mut buf));
326         // Just check that the correct number of objects are written
327         assert_eq!(&buf[0..12], &*pack_header(2));
328     }
329 
330     #[test]
insert_commit_write_buf()331     fn insert_commit_write_buf() {
332         let (_td, repo) = crate::test::repo_init();
333         let mut builder = t!(repo.packbuilder());
334         let mut buf = Buf::new();
335         let (commit, _tree) = crate::test::commit(&repo);
336         // will insert the commit, its tree and the blob, 3 objects
337         t!(builder.insert_commit(commit));
338         assert_eq!(builder.object_count(), 3);
339         t!(builder.write_buf(&mut buf));
340         // Just check that the correct number of objects are written
341         assert_eq!(&buf[0..12], &*pack_header(3));
342     }
343 
344     #[test]
progress_callback()345     fn progress_callback() {
346         let mut progress_called = false;
347         {
348             let (_td, repo) = crate::test::repo_init();
349             let mut builder = t!(repo.packbuilder());
350             let (commit, _tree) = crate::test::commit(&repo);
351             t!(builder.set_progress_callback(|_, _, _| {
352                 progress_called = true;
353                 true
354             }));
355             t!(builder.insert_commit(commit));
356             t!(builder.write_buf(&mut Buf::new()));
357         }
358         assert_eq!(progress_called, true);
359     }
360 
361     #[test]
clear_progress_callback()362     fn clear_progress_callback() {
363         let mut progress_called = false;
364         {
365             let (_td, repo) = crate::test::repo_init();
366             let mut builder = t!(repo.packbuilder());
367             let (commit, _tree) = crate::test::commit(&repo);
368             t!(builder.set_progress_callback(|_, _, _| {
369                 progress_called = true;
370                 true
371             }));
372             t!(builder.unset_progress_callback());
373             t!(builder.insert_commit(commit));
374             t!(builder.write_buf(&mut Buf::new()));
375         }
376         assert_eq!(progress_called, false);
377     }
378 
379     #[test]
set_threads()380     fn set_threads() {
381         let (_td, repo) = crate::test::repo_init();
382         let mut builder = t!(repo.packbuilder());
383         let used = builder.set_threads(4);
384         // Will be 1 if not compiled with threading.
385         assert!(used == 1 || used == 4);
386     }
387 }
388