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