git2/
packbuilder.rs

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)]
14/// Stages that are reported by the `PackBuilder` progress callback.
15pub enum PackBuilderStage {
16    /// Adding objects to the pack
17    AddingObjects,
18    /// Deltafication of the pack
19    Deltafication,
20}
21
22pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a;
23pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a;
24
25/// A builder for creating a packfile
26pub 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    /// Insert a single object. For an optimal pack it's mandatory to insert
34    /// objects in recency order, commits followed by trees and blobs.
35    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    /// Insert a root tree object. This will add the tree as well as all
44    /// referenced trees and blobs.
45    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    /// Insert a commit object. This will add a commit as well as the completed
53    /// referenced tree.
54    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    /// Insert objects as given by the walk. Those commits and all objects they
62    /// reference will be inserted into the packbuilder.
63    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    /// Recursively insert an object and its referenced objects. Insert the
71    /// object as well as any object it references.
72    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    /// Write the contents of the packfile to an in-memory buffer. The contents
81    /// of the buffer will become a valid packfile, even though there will be
82    /// no attached index.
83    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    /// Write the new pack and corresponding index file to path.
91    /// To set a progress callback, use `set_progress_callback` before calling this method.
92    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    /// Create the new pack and pass each object to the callback.
111    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    /// `progress` will be called with progress information during pack
129    /// building. Be aware that this is called inline with pack building
130    /// operations, so performance may be affected.
131    ///
132    /// There can only be one progress callback attached, this will replace any
133    /// existing one. See `unset_progress_callback` to remove the current
134    /// progress callback without attaching a new one.
135    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    /// Remove the current progress callback.  See `set_progress_callback` to
154    /// set the progress callback.
155    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    /// Set the number of threads to be used.
168    ///
169    /// Returns the number of threads to be used.
170    pub fn set_threads(&mut self, threads: u32) -> u32 {
171        unsafe { raw::git_packbuilder_set_threads(self.raw, threads) }
172    }
173
174    /// Get the total number of objects the packbuilder will write out.
175    pub fn object_count(&self) -> usize {
176        unsafe { raw::git_packbuilder_object_count(self.raw) }
177    }
178
179    /// Get the number of objects the packbuilder has already written out.
180    pub fn written(&self) -> usize {
181        unsafe { raw::git_packbuilder_written(self.raw) }
182    }
183
184    /// Get the packfile's hash. A packfile's name is derived from the sorted
185    /// hashing of all object names. This is only correct after the packfile
186    /// has been written.
187    #[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    /// Get the unique name for the resulting packfile.
198    ///
199    /// The packfile's name is derived from the packfile's content. This is only
200    /// correct after the packfile has been written.
201    ///
202    /// Returns `None` if the packfile has not been written or if the name is
203    /// not valid utf-8.
204    pub fn name(&self) -> Option<&str> {
205        self.name_bytes().and_then(|s| str::from_utf8(s).ok())
206    }
207
208    /// Get the unique name for the resulting packfile, in bytes.
209    ///
210    /// The packfile's name is derived from the packfile's content. This is only
211    /// correct after the packfile has been written.
212    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    // hash of a packfile constructed without any objects in it
299    const EMPTY_PACKFILE_OID: &str = "029d08823bd8a8eab510ad6ac75c823cfd3ed31e";
300
301    fn pack_header(len: u8) -> Vec<u8> {
302        [].iter()
303            .chain(b"PACK") // signature
304            .chain(&[0, 0, 0, 2]) // version number
305            .chain(&[0, 0, 0, len]) // number of objects
306            .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, // ^
315                0xd8, 0xa8, 0xea, 0xb5, 0x10, // | SHA-1 of the zero
316                0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header
317                0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
318            ]) // v
319            .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        // Just check that the correct number of objects are written
377        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        // will insert the tree itself and the blob, 2 objects
387        t!(builder.insert_tree(tree));
388        assert_eq!(builder.object_count(), 2);
389        t!(builder.write_buf(&mut buf));
390        // Just check that the correct number of objects are written
391        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        // will insert the commit, its tree and the blob, 3 objects
401        t!(builder.insert_commit(commit));
402        assert_eq!(builder.object_count(), 3);
403        t!(builder.write_buf(&mut buf));
404        // Just check that the correct number of objects are written
405        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        // will insert the tree itself and the blob, 2 objects
425        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        // will insert the commit, its tree and the blob, 3 objects
437        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        // Will be 1 if not compiled with threading.
501        assert!(used == 1 || used == 4);
502    }
503}