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