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