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