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