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