1 use std::io;
2 use std::marker;
3 use std::mem::MaybeUninit;
4 use std::ptr;
5 use std::slice;
6 
7 use std::ffi::CString;
8 
9 use libc::{c_char, c_int, c_void, size_t};
10 
11 use crate::panic;
12 use crate::util::Binding;
13 use crate::{raw, Error, IndexerProgress, Mempack, Object, ObjectType, Oid, Progress};
14 
15 /// A structure to represent a git object database
16 pub struct Odb<'repo> {
17     raw: *mut raw::git_odb,
18     _marker: marker::PhantomData<Object<'repo>>,
19 }
20 
21 impl<'repo> Binding for Odb<'repo> {
22     type Raw = *mut raw::git_odb;
23 
from_raw(raw: *mut raw::git_odb) -> Odb<'repo>24     unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> {
25         Odb {
26             raw: raw,
27             _marker: marker::PhantomData,
28         }
29     }
raw(&self) -> *mut raw::git_odb30     fn raw(&self) -> *mut raw::git_odb {
31         self.raw
32     }
33 }
34 
35 impl<'repo> Drop for Odb<'repo> {
drop(&mut self)36     fn drop(&mut self) {
37         unsafe { raw::git_odb_free(self.raw) }
38     }
39 }
40 
41 impl<'repo> Odb<'repo> {
42     /// Creates an object database without any backends.
new<'a>() -> Result<Odb<'a>, Error>43     pub fn new<'a>() -> Result<Odb<'a>, Error> {
44         crate::init();
45         unsafe {
46             let mut out = ptr::null_mut();
47             try_call!(raw::git_odb_new(&mut out));
48             Ok(Odb::from_raw(out))
49         }
50     }
51 
52     /// Create object database reading stream.
53     ///
54     /// Note that most backends do not support streaming reads because they store their objects as compressed/delta'ed blobs.
55     /// If the backend does not support streaming reads, use the `read` method instead.
reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error>56     pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> {
57         let mut out = ptr::null_mut();
58         let mut size = 0usize;
59         let mut otype: raw::git_object_t = ObjectType::Any.raw();
60         unsafe {
61             try_call!(raw::git_odb_open_rstream(
62                 &mut out,
63                 &mut size,
64                 &mut otype,
65                 self.raw,
66                 oid.raw()
67             ));
68             Ok((
69                 OdbReader::from_raw(out),
70                 size,
71                 ObjectType::from_raw(otype).unwrap(),
72             ))
73         }
74     }
75 
76     /// Create object database writing stream.
77     ///
78     /// The type and final length of the object must be specified when opening the stream.
79     /// If the backend does not support streaming writes, use the `write` method instead.
writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error>80     pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> {
81         let mut out = ptr::null_mut();
82         unsafe {
83             try_call!(raw::git_odb_open_wstream(
84                 &mut out,
85                 self.raw,
86                 size as raw::git_object_size_t,
87                 obj_type.raw()
88             ));
89             Ok(OdbWriter::from_raw(out))
90         }
91     }
92 
93     /// Iterate over all objects in the object database.s
foreach<C>(&self, mut callback: C) -> Result<(), Error> where C: FnMut(&Oid) -> bool,94     pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error>
95     where
96         C: FnMut(&Oid) -> bool,
97     {
98         unsafe {
99             let mut data = ForeachCbData {
100                 callback: &mut callback,
101             };
102             let cb: raw::git_odb_foreach_cb = Some(foreach_cb);
103             try_call!(raw::git_odb_foreach(
104                 self.raw(),
105                 cb,
106                 &mut data as *mut _ as *mut _
107             ));
108             Ok(())
109         }
110     }
111 
112     /// Read an object from the database.
read(&self, oid: Oid) -> Result<OdbObject<'_>, Error>113     pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> {
114         let mut out = ptr::null_mut();
115         unsafe {
116             try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw()));
117             Ok(OdbObject::from_raw(out))
118         }
119     }
120 
121     /// Reads the header of an object from the database
122     /// without reading the full content.
read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error>123     pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> {
124         let mut size: usize = 0;
125         let mut kind_id: i32 = ObjectType::Any.raw();
126 
127         unsafe {
128             try_call!(raw::git_odb_read_header(
129                 &mut size as *mut size_t,
130                 &mut kind_id as *mut raw::git_object_t,
131                 self.raw,
132                 oid.raw()
133             ));
134 
135             Ok((size, ObjectType::from_raw(kind_id).unwrap()))
136         }
137     }
138 
139     /// Write an object to the database.
write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error>140     pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> {
141         unsafe {
142             let mut out = raw::git_oid {
143                 id: [0; raw::GIT_OID_RAWSZ],
144             };
145             try_call!(raw::git_odb_write(
146                 &mut out,
147                 self.raw,
148                 data.as_ptr() as *const c_void,
149                 data.len(),
150                 kind.raw()
151             ));
152             Ok(Oid::from_raw(&mut out))
153         }
154     }
155 
156     /// Create stream for writing a pack file to the ODB
packwriter(&self) -> Result<OdbPackwriter<'_>, Error>157     pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> {
158         let mut out = ptr::null_mut();
159         let progress = MaybeUninit::uninit();
160         let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
161         let progress_payload = Box::new(OdbPackwriterCb { cb: None });
162         let progress_payload_ptr = Box::into_raw(progress_payload);
163 
164         unsafe {
165             try_call!(raw::git_odb_write_pack(
166                 &mut out,
167                 self.raw,
168                 progress_cb,
169                 progress_payload_ptr as *mut c_void
170             ));
171         }
172 
173         Ok(OdbPackwriter {
174             raw: out,
175             progress,
176             progress_payload_ptr,
177         })
178     }
179 
180     /// Checks if the object database has an object.
exists(&self, oid: Oid) -> bool181     pub fn exists(&self, oid: Oid) -> bool {
182         unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
183     }
184 
185     /// Potentially finds an object that starts with the given prefix.
exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error>186     pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> {
187         unsafe {
188             let mut out = raw::git_oid {
189                 id: [0; raw::GIT_OID_RAWSZ],
190             };
191             try_call!(raw::git_odb_exists_prefix(
192                 &mut out,
193                 self.raw,
194                 short_oid.raw(),
195                 len
196             ));
197             Ok(Oid::from_raw(&out))
198         }
199     }
200 
201     /// Refresh the object database.
202     /// This should never be needed, and is
203     /// provided purely for convenience.
204     /// The object database will automatically
205     /// refresh when an object is not found when
206     /// requested.
refresh(&self) -> Result<(), Error>207     pub fn refresh(&self) -> Result<(), Error> {
208         unsafe {
209             try_call!(raw::git_odb_refresh(self.raw));
210             Ok(())
211         }
212     }
213 
214     /// Adds an alternate disk backend to the object database.
add_disk_alternate(&self, path: &str) -> Result<(), Error>215     pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> {
216         unsafe {
217             let path = CString::new(path)?;
218             try_call!(raw::git_odb_add_disk_alternate(self.raw, path));
219             Ok(())
220         }
221     }
222 
223     /// Create a new mempack backend, and add it to this odb with the given
224     /// priority. Higher values give the backend higher precedence. The default
225     /// loose and pack backends have priorities 1 and 2 respectively (hard-coded
226     /// in libgit2). A reference to the new mempack backend is returned on
227     /// success. The lifetime of the backend must be contained within the
228     /// lifetime of this odb, since deletion of the odb will also result in
229     /// deletion of the mempack backend.
230     ///
231     /// Here is an example that fails to compile because it tries to hold the
232     /// mempack reference beyond the odb's lifetime:
233     ///
234     /// ```compile_fail
235     /// use git2::Odb;
236     /// let mempack = {
237     ///     let odb = Odb::new().unwrap();
238     ///     odb.add_new_mempack_backend(1000).unwrap()
239     /// };
240     /// ```
add_new_mempack_backend<'odb>( &'odb self, priority: i32, ) -> Result<Mempack<'odb>, Error>241     pub fn add_new_mempack_backend<'odb>(
242         &'odb self,
243         priority: i32,
244     ) -> Result<Mempack<'odb>, Error> {
245         unsafe {
246             let mut mempack = ptr::null_mut();
247             // The mempack backend object in libgit2 is only ever freed by an
248             // odb that has the backend in its list. So to avoid potentially
249             // leaking the mempack backend, this API ensures that the backend
250             // is added to the odb before returning it. The lifetime of the
251             // mempack is also bound to the lifetime of the odb, so that users
252             // can't end up with a dangling reference to a mempack object that
253             // was actually freed when the odb was destroyed.
254             try_call!(raw::git_mempack_new(&mut mempack));
255             try_call!(raw::git_odb_add_backend(
256                 self.raw,
257                 mempack,
258                 priority as c_int
259             ));
260             Ok(Mempack::from_raw(mempack))
261         }
262     }
263 }
264 
265 /// An object from the Object Database.
266 pub struct OdbObject<'a> {
267     raw: *mut raw::git_odb_object,
268     _marker: marker::PhantomData<Object<'a>>,
269 }
270 
271 impl<'a> Binding for OdbObject<'a> {
272     type Raw = *mut raw::git_odb_object;
273 
from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a>274     unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> {
275         OdbObject {
276             raw: raw,
277             _marker: marker::PhantomData,
278         }
279     }
280 
raw(&self) -> *mut raw::git_odb_object281     fn raw(&self) -> *mut raw::git_odb_object {
282         self.raw
283     }
284 }
285 
286 impl<'a> Drop for OdbObject<'a> {
drop(&mut self)287     fn drop(&mut self) {
288         unsafe { raw::git_odb_object_free(self.raw) }
289     }
290 }
291 
292 impl<'a> OdbObject<'a> {
293     /// Get the object type.
kind(&self) -> ObjectType294     pub fn kind(&self) -> ObjectType {
295         unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() }
296     }
297 
298     /// Get the object size.
len(&self) -> usize299     pub fn len(&self) -> usize {
300         unsafe { raw::git_odb_object_size(self.raw) }
301     }
302 
303     /// Get the object data.
data(&self) -> &[u8]304     pub fn data(&self) -> &[u8] {
305         unsafe {
306             let size = self.len();
307             let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8;
308             let buffer = slice::from_raw_parts(ptr, size);
309             return buffer;
310         }
311     }
312 
313     /// Get the object id.
id(&self) -> Oid314     pub fn id(&self) -> Oid {
315         unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) }
316     }
317 }
318 
319 /// A structure to represent a git ODB rstream
320 pub struct OdbReader<'repo> {
321     raw: *mut raw::git_odb_stream,
322     _marker: marker::PhantomData<Object<'repo>>,
323 }
324 
325 impl<'repo> Binding for OdbReader<'repo> {
326     type Raw = *mut raw::git_odb_stream;
327 
from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo>328     unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> {
329         OdbReader {
330             raw: raw,
331             _marker: marker::PhantomData,
332         }
333     }
raw(&self) -> *mut raw::git_odb_stream334     fn raw(&self) -> *mut raw::git_odb_stream {
335         self.raw
336     }
337 }
338 
339 impl<'repo> Drop for OdbReader<'repo> {
drop(&mut self)340     fn drop(&mut self) {
341         unsafe { raw::git_odb_stream_free(self.raw) }
342     }
343 }
344 
345 impl<'repo> io::Read for OdbReader<'repo> {
read(&mut self, buf: &mut [u8]) -> io::Result<usize>346     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
347         unsafe {
348             let ptr = buf.as_ptr() as *mut c_char;
349             let len = buf.len();
350             let res = raw::git_odb_stream_read(self.raw, ptr, len);
351             if res < 0 {
352                 Err(io::Error::new(io::ErrorKind::Other, "Read error"))
353             } else {
354                 Ok(len)
355             }
356         }
357     }
358 }
359 
360 /// A structure to represent a git ODB wstream
361 pub struct OdbWriter<'repo> {
362     raw: *mut raw::git_odb_stream,
363     _marker: marker::PhantomData<Object<'repo>>,
364 }
365 
366 impl<'repo> OdbWriter<'repo> {
367     /// Finish writing to an ODB stream
368     ///
369     /// This method can be used to finalize writing object to the database and get an identifier.
370     /// The object will take its final name and will be available to the odb.
371     /// This method will fail if the total number of received bytes differs from the size declared with odb_writer()
372     /// Attepting write after finishing will be ignored.
finalize(&mut self) -> Result<Oid, Error>373     pub fn finalize(&mut self) -> Result<Oid, Error> {
374         let mut raw = raw::git_oid {
375             id: [0; raw::GIT_OID_RAWSZ],
376         };
377         unsafe {
378             try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw));
379             Ok(Binding::from_raw(&raw as *const _))
380         }
381     }
382 }
383 
384 impl<'repo> Binding for OdbWriter<'repo> {
385     type Raw = *mut raw::git_odb_stream;
386 
from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo>387     unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> {
388         OdbWriter {
389             raw: raw,
390             _marker: marker::PhantomData,
391         }
392     }
raw(&self) -> *mut raw::git_odb_stream393     fn raw(&self) -> *mut raw::git_odb_stream {
394         self.raw
395     }
396 }
397 
398 impl<'repo> Drop for OdbWriter<'repo> {
drop(&mut self)399     fn drop(&mut self) {
400         unsafe { raw::git_odb_stream_free(self.raw) }
401     }
402 }
403 
404 impl<'repo> io::Write for OdbWriter<'repo> {
write(&mut self, buf: &[u8]) -> io::Result<usize>405     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
406         unsafe {
407             let ptr = buf.as_ptr() as *const c_char;
408             let len = buf.len();
409             let res = raw::git_odb_stream_write(self.raw, ptr, len);
410             if res < 0 {
411                 Err(io::Error::new(io::ErrorKind::Other, "Write error"))
412             } else {
413                 Ok(buf.len())
414             }
415         }
416     }
flush(&mut self) -> io::Result<()>417     fn flush(&mut self) -> io::Result<()> {
418         Ok(())
419     }
420 }
421 
422 struct OdbPackwriterCb<'repo> {
423     cb: Option<Box<IndexerProgress<'repo>>>,
424 }
425 
426 /// A stream to write a packfile to the ODB
427 pub struct OdbPackwriter<'repo> {
428     raw: *mut raw::git_odb_writepack,
429     progress: MaybeUninit<raw::git_indexer_progress>,
430     progress_payload_ptr: *mut OdbPackwriterCb<'repo>,
431 }
432 
433 impl<'repo> OdbPackwriter<'repo> {
434     /// Finish writing the packfile
commit(&mut self) -> Result<i32, Error>435     pub fn commit(&mut self) -> Result<i32, Error> {
436         unsafe {
437             let writepack = &*self.raw;
438             let res = match writepack.commit {
439                 Some(commit) => commit(self.raw, self.progress.as_mut_ptr()),
440                 None => -1,
441             };
442 
443             if res < 0 {
444                 Err(Error::last_error(res).unwrap())
445             } else {
446                 Ok(res)
447             }
448         }
449     }
450 
451     /// The callback through which progress is monitored. Be aware that this is
452     /// called inline, so performance may be affected.
progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo> where F: FnMut(Progress<'_>) -> bool + 'repo,453     pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo>
454     where
455         F: FnMut(Progress<'_>) -> bool + 'repo,
456     {
457         let progress_payload =
458             unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
459 
460         progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>);
461         self
462     }
463 }
464 
465 impl<'repo> io::Write for OdbPackwriter<'repo> {
write(&mut self, buf: &[u8]) -> io::Result<usize>466     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
467         unsafe {
468             let ptr = buf.as_ptr() as *mut c_void;
469             let len = buf.len();
470 
471             let writepack = &*self.raw;
472             let res = match writepack.append {
473                 Some(append) => append(self.raw, ptr, len, self.progress.as_mut_ptr()),
474                 None => -1,
475             };
476 
477             if res < 0 {
478                 Err(io::Error::new(io::ErrorKind::Other, "Write error"))
479             } else {
480                 Ok(buf.len())
481             }
482         }
483     }
flush(&mut self) -> io::Result<()>484     fn flush(&mut self) -> io::Result<()> {
485         Ok(())
486     }
487 }
488 
489 impl<'repo> Drop for OdbPackwriter<'repo> {
drop(&mut self)490     fn drop(&mut self) {
491         unsafe {
492             let writepack = &*self.raw;
493             match writepack.free {
494                 Some(free) => free(self.raw),
495                 None => (),
496             };
497 
498             Box::from_raw(self.progress_payload_ptr);
499         }
500     }
501 }
502 
503 pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
504 
505 struct ForeachCbData<'a> {
506     pub callback: &'a mut ForeachCb<'a>,
507 }
508 
foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int509 extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int {
510     panic::wrap(|| unsafe {
511         let data = &mut *(payload as *mut ForeachCbData<'_>);
512         let res = {
513             let callback = &mut data.callback;
514             callback(&Binding::from_raw(id))
515         };
516 
517         if res {
518             0
519         } else {
520             1
521         }
522     })
523     .unwrap_or(1)
524 }
525 
write_pack_progress_cb( stats: *const raw::git_indexer_progress, payload: *mut c_void, ) -> c_int526 extern "C" fn write_pack_progress_cb(
527     stats: *const raw::git_indexer_progress,
528     payload: *mut c_void,
529 ) -> c_int {
530     let ok = panic::wrap(|| unsafe {
531         let payload = &mut *(payload as *mut OdbPackwriterCb<'_>);
532 
533         let callback = match payload.cb {
534             Some(ref mut cb) => cb,
535             None => return true,
536         };
537 
538         let progress: Progress<'_> = Binding::from_raw(stats);
539         callback(progress)
540     });
541     if ok == Some(true) {
542         0
543     } else {
544         -1
545     }
546 }
547 
548 #[cfg(test)]
549 mod tests {
550     use crate::{Buf, ObjectType, Oid, Repository};
551     use std::io::prelude::*;
552     use tempfile::TempDir;
553 
554     #[test]
read()555     fn read() {
556         let td = TempDir::new().unwrap();
557         let repo = Repository::init(td.path()).unwrap();
558         let dat = [4, 3, 5, 6, 9];
559         let id = repo.blob(&dat).unwrap();
560         let db = repo.odb().unwrap();
561         let obj = db.read(id).unwrap();
562         let data = obj.data();
563         let size = obj.len();
564         assert_eq!(size, 5);
565         assert_eq!(dat, data);
566         assert_eq!(id, obj.id());
567     }
568 
569     #[test]
read_header()570     fn read_header() {
571         let td = TempDir::new().unwrap();
572         let repo = Repository::init(td.path()).unwrap();
573         let dat = [4, 3, 5, 6, 9];
574         let id = repo.blob(&dat).unwrap();
575         let db = repo.odb().unwrap();
576         let (size, kind) = db.read_header(id).unwrap();
577 
578         assert_eq!(size, 5);
579         assert_eq!(kind, ObjectType::Blob);
580     }
581 
582     #[test]
write()583     fn write() {
584         let td = TempDir::new().unwrap();
585         let repo = Repository::init(td.path()).unwrap();
586         let dat = [4, 3, 5, 6, 9];
587         let db = repo.odb().unwrap();
588         let id = db.write(ObjectType::Blob, &dat).unwrap();
589         let blob = repo.find_blob(id).unwrap();
590         assert_eq!(blob.content(), dat);
591     }
592 
593     #[test]
writer()594     fn writer() {
595         let td = TempDir::new().unwrap();
596         let repo = Repository::init(td.path()).unwrap();
597         let dat = [4, 3, 5, 6, 9];
598         let db = repo.odb().unwrap();
599         let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap();
600         let wl = ws.write(&dat[0..3]).unwrap();
601         assert_eq!(wl, 3);
602         let wl = ws.write(&dat[3..5]).unwrap();
603         assert_eq!(wl, 2);
604         let id = ws.finalize().unwrap();
605         let blob = repo.find_blob(id).unwrap();
606         assert_eq!(blob.content(), dat);
607     }
608 
609     #[test]
exists()610     fn exists() {
611         let td = TempDir::new().unwrap();
612         let repo = Repository::init(td.path()).unwrap();
613         let dat = [4, 3, 5, 6, 9];
614         let db = repo.odb().unwrap();
615         let id = db.write(ObjectType::Blob, &dat).unwrap();
616         assert!(db.exists(id));
617     }
618 
619     #[test]
exists_prefix()620     fn exists_prefix() {
621         let td = TempDir::new().unwrap();
622         let repo = Repository::init(td.path()).unwrap();
623         let dat = [4, 3, 5, 6, 9];
624         let db = repo.odb().unwrap();
625         let id = db.write(ObjectType::Blob, &dat).unwrap();
626         let id_prefix_str = &id.to_string()[0..10];
627         let id_prefix = Oid::from_str(id_prefix_str).unwrap();
628         let found_oid = db.exists_prefix(id_prefix, 10).unwrap();
629         assert_eq!(found_oid, id);
630     }
631 
632     #[test]
packwriter()633     fn packwriter() {
634         let (_td, repo_source) = crate::test::repo_init();
635         let (_td, repo_target) = crate::test::repo_init();
636         let mut builder = t!(repo_source.packbuilder());
637         let mut buf = Buf::new();
638         let (commit_source_id, _tree) = crate::test::commit(&repo_source);
639         t!(builder.insert_object(commit_source_id, None));
640         t!(builder.write_buf(&mut buf));
641         let db = repo_target.odb().unwrap();
642         let mut packwriter = db.packwriter().unwrap();
643         packwriter.write(&buf).unwrap();
644         packwriter.commit().unwrap();
645         let commit_target = repo_target.find_commit(commit_source_id).unwrap();
646         assert_eq!(commit_target.id(), commit_source_id);
647     }
648 
649     #[test]
packwriter_progress()650     fn packwriter_progress() {
651         let mut progress_called = false;
652         {
653             let (_td, repo_source) = crate::test::repo_init();
654             let (_td, repo_target) = crate::test::repo_init();
655             let mut builder = t!(repo_source.packbuilder());
656             let mut buf = Buf::new();
657             let (commit_source_id, _tree) = crate::test::commit(&repo_source);
658             t!(builder.insert_object(commit_source_id, None));
659             t!(builder.write_buf(&mut buf));
660             let db = repo_target.odb().unwrap();
661             let mut packwriter = db.packwriter().unwrap();
662             packwriter.progress(|_| {
663                 progress_called = true;
664                 true
665             });
666             packwriter.write(&buf).unwrap();
667             packwriter.commit().unwrap();
668         }
669         assert_eq!(progress_called, true);
670     }
671 
672     #[test]
write_with_mempack()673     fn write_with_mempack() {
674         use crate::{Buf, ResetType};
675         use std::io::Write;
676         use std::path::Path;
677 
678         // Create a repo, add a mempack backend
679         let (_td, repo) = crate::test::repo_init();
680         let odb = repo.odb().unwrap();
681         let mempack = odb.add_new_mempack_backend(1000).unwrap();
682 
683         // Sanity check that foo doesn't exist initially
684         let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
685         assert!(!foo_file.exists());
686 
687         // Make a commit that adds foo. This writes new stuff into the mempack
688         // backend.
689         let (oid1, _id) = crate::test::commit(&repo);
690         let commit1 = repo.find_commit(oid1).unwrap();
691         t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
692         assert!(foo_file.exists());
693 
694         // Dump the mempack modifications into a buf, and reset it. This "erases"
695         // commit-related objects from the repository. Ensure the commit appears
696         // to have become invalid, by checking for failure in `reset --hard`.
697         let mut buf = Buf::new();
698         mempack.dump(&repo, &mut buf).unwrap();
699         mempack.reset().unwrap();
700         assert!(repo
701             .reset(commit1.as_object(), ResetType::Hard, None)
702             .is_err());
703 
704         // Write the buf into a packfile in the repo. This brings back the
705         // missing objects, and we verify everything is good again.
706         let mut packwriter = odb.packwriter().unwrap();
707         packwriter.write(&buf).unwrap();
708         packwriter.commit().unwrap();
709         t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
710         assert!(foo_file.exists());
711     }
712 }
713