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