1use std::io;
2use std::marker;
3use std::ptr;
4use std::slice;
5
6use std::ffi::CString;
7
8use libc::{c_char, c_int, c_uint, c_void, size_t};
9
10use crate::panic;
11use crate::util::Binding;
12use crate::{
13 raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress,
14};
15
16pub struct Odb<'repo> {
18 raw: *mut raw::git_odb,
19 _marker: marker::PhantomData<Object<'repo>>,
20}
21
22unsafe impl<'repo> Send for Odb<'repo> {}
24unsafe impl<'repo> Sync for Odb<'repo> {}
25
26impl<'repo> Binding for Odb<'repo> {
27 type Raw = *mut raw::git_odb;
28
29 unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> {
30 Odb {
31 raw,
32 _marker: marker::PhantomData,
33 }
34 }
35 fn raw(&self) -> *mut raw::git_odb {
36 self.raw
37 }
38}
39
40impl<'repo> Drop for Odb<'repo> {
41 fn drop(&mut self) {
42 unsafe { raw::git_odb_free(self.raw) }
43 }
44}
45
46impl<'repo> Odb<'repo> {
47 pub fn new<'a>() -> Result<Odb<'a>, Error> {
49 crate::init();
50 unsafe {
51 let mut out = ptr::null_mut();
52 try_call!(raw::git_odb_new(&mut out));
53 Ok(Odb::from_raw(out))
54 }
55 }
56
57 pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> {
62 let mut out = ptr::null_mut();
63 let mut size = 0usize;
64 let mut otype: raw::git_object_t = ObjectType::Any.raw();
65 unsafe {
66 try_call!(raw::git_odb_open_rstream(
67 &mut out,
68 &mut size,
69 &mut otype,
70 self.raw,
71 oid.raw()
72 ));
73 Ok((
74 OdbReader::from_raw(out),
75 size,
76 ObjectType::from_raw(otype).unwrap(),
77 ))
78 }
79 }
80
81 pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> {
86 let mut out = ptr::null_mut();
87 unsafe {
88 try_call!(raw::git_odb_open_wstream(
89 &mut out,
90 self.raw,
91 size as raw::git_object_size_t,
92 obj_type.raw()
93 ));
94 Ok(OdbWriter::from_raw(out))
95 }
96 }
97
98 pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error>
100 where
101 C: FnMut(&Oid) -> bool,
102 {
103 unsafe {
104 let mut data = ForeachCbData {
105 callback: &mut callback,
106 };
107 let cb: raw::git_odb_foreach_cb = Some(foreach_cb);
108 try_call!(raw::git_odb_foreach(
109 self.raw(),
110 cb,
111 &mut data as *mut _ as *mut _
112 ));
113 Ok(())
114 }
115 }
116
117 pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> {
119 let mut out = ptr::null_mut();
120 unsafe {
121 try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw()));
122 Ok(OdbObject::from_raw(out))
123 }
124 }
125
126 pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> {
129 let mut size: usize = 0;
130 let mut kind_id: i32 = ObjectType::Any.raw();
131
132 unsafe {
133 try_call!(raw::git_odb_read_header(
134 &mut size as *mut size_t,
135 &mut kind_id as *mut raw::git_object_t,
136 self.raw,
137 oid.raw()
138 ));
139
140 Ok((size, ObjectType::from_raw(kind_id).unwrap()))
141 }
142 }
143
144 pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> {
146 unsafe {
147 let mut out = raw::git_oid {
148 id: [0; raw::GIT_OID_RAWSZ],
149 };
150 try_call!(raw::git_odb_write(
151 &mut out,
152 self.raw,
153 data.as_ptr() as *const c_void,
154 data.len(),
155 kind.raw()
156 ));
157 Ok(Oid::from_raw(&mut out))
158 }
159 }
160
161 pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> {
163 let mut out = ptr::null_mut();
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: Default::default(),
180 progress_payload_ptr,
181 })
182 }
183
184 pub fn exists(&self, oid: Oid) -> bool {
186 unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
187 }
188
189 pub fn exists_ext(&self, oid: Oid, flags: OdbLookupFlags) -> bool {
191 unsafe { raw::git_odb_exists_ext(self.raw, oid.raw(), flags.bits() as c_uint) != 0 }
192 }
193
194 pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> {
196 unsafe {
197 let mut out = raw::git_oid {
198 id: [0; raw::GIT_OID_RAWSZ],
199 };
200 try_call!(raw::git_odb_exists_prefix(
201 &mut out,
202 self.raw,
203 short_oid.raw(),
204 len
205 ));
206 Ok(Oid::from_raw(&out))
207 }
208 }
209
210 pub fn refresh(&self) -> Result<(), Error> {
217 unsafe {
218 try_call!(raw::git_odb_refresh(self.raw));
219 Ok(())
220 }
221 }
222
223 pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> {
225 unsafe {
226 let path = CString::new(path)?;
227 try_call!(raw::git_odb_add_disk_alternate(self.raw, path));
228 Ok(())
229 }
230 }
231
232 pub fn add_new_mempack_backend<'odb>(
251 &'odb self,
252 priority: i32,
253 ) -> Result<Mempack<'odb>, Error> {
254 unsafe {
255 let mut mempack = ptr::null_mut();
256 try_call!(raw::git_mempack_new(&mut mempack));
264 try_call!(raw::git_odb_add_backend(
265 self.raw,
266 mempack,
267 priority as c_int
268 ));
269 Ok(Mempack::from_raw(mempack))
270 }
271 }
272}
273
274pub struct OdbObject<'a> {
276 raw: *mut raw::git_odb_object,
277 _marker: marker::PhantomData<Object<'a>>,
278}
279
280impl<'a> Binding for OdbObject<'a> {
281 type Raw = *mut raw::git_odb_object;
282
283 unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> {
284 OdbObject {
285 raw,
286 _marker: marker::PhantomData,
287 }
288 }
289
290 fn raw(&self) -> *mut raw::git_odb_object {
291 self.raw
292 }
293}
294
295impl<'a> Drop for OdbObject<'a> {
296 fn drop(&mut self) {
297 unsafe { raw::git_odb_object_free(self.raw) }
298 }
299}
300
301impl<'a> OdbObject<'a> {
302 pub fn kind(&self) -> ObjectType {
304 unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() }
305 }
306
307 pub fn len(&self) -> usize {
309 unsafe { raw::git_odb_object_size(self.raw) }
310 }
311
312 pub fn data(&self) -> &[u8] {
314 unsafe {
315 let size = self.len();
316 let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8;
317 let buffer = slice::from_raw_parts(ptr, size);
318 return buffer;
319 }
320 }
321
322 pub fn id(&self) -> Oid {
324 unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) }
325 }
326}
327
328pub struct OdbReader<'repo> {
330 raw: *mut raw::git_odb_stream,
331 _marker: marker::PhantomData<Object<'repo>>,
332}
333
334unsafe impl<'repo> Send for OdbReader<'repo> {}
337
338impl<'repo> Binding for OdbReader<'repo> {
339 type Raw = *mut raw::git_odb_stream;
340
341 unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> {
342 OdbReader {
343 raw,
344 _marker: marker::PhantomData,
345 }
346 }
347 fn raw(&self) -> *mut raw::git_odb_stream {
348 self.raw
349 }
350}
351
352impl<'repo> Drop for OdbReader<'repo> {
353 fn drop(&mut self) {
354 unsafe { raw::git_odb_stream_free(self.raw) }
355 }
356}
357
358impl<'repo> io::Read for OdbReader<'repo> {
359 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
360 unsafe {
361 let ptr = buf.as_ptr() as *mut c_char;
362 let len = buf.len();
363 let res = raw::git_odb_stream_read(self.raw, ptr, len);
364 if res < 0 {
365 Err(io::Error::new(io::ErrorKind::Other, "Read error"))
366 } else {
367 Ok(res as _)
368 }
369 }
370 }
371}
372
373pub struct OdbWriter<'repo> {
375 raw: *mut raw::git_odb_stream,
376 _marker: marker::PhantomData<Object<'repo>>,
377}
378
379unsafe impl<'repo> Send for OdbWriter<'repo> {}
382
383impl<'repo> OdbWriter<'repo> {
384 pub fn finalize(&mut self) -> Result<Oid, Error> {
391 let mut raw = raw::git_oid {
392 id: [0; raw::GIT_OID_RAWSZ],
393 };
394 unsafe {
395 try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw));
396 Ok(Binding::from_raw(&raw as *const _))
397 }
398 }
399}
400
401impl<'repo> Binding for OdbWriter<'repo> {
402 type Raw = *mut raw::git_odb_stream;
403
404 unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> {
405 OdbWriter {
406 raw,
407 _marker: marker::PhantomData,
408 }
409 }
410 fn raw(&self) -> *mut raw::git_odb_stream {
411 self.raw
412 }
413}
414
415impl<'repo> Drop for OdbWriter<'repo> {
416 fn drop(&mut self) {
417 unsafe { raw::git_odb_stream_free(self.raw) }
418 }
419}
420
421impl<'repo> io::Write for OdbWriter<'repo> {
422 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
423 unsafe {
424 let ptr = buf.as_ptr() as *const c_char;
425 let len = buf.len();
426 let res = raw::git_odb_stream_write(self.raw, ptr, len);
427 if res < 0 {
428 Err(io::Error::new(io::ErrorKind::Other, "Write error"))
429 } else {
430 Ok(buf.len())
431 }
432 }
433 }
434 fn flush(&mut self) -> io::Result<()> {
435 Ok(())
436 }
437}
438
439pub(crate) struct OdbPackwriterCb<'repo> {
440 pub(crate) cb: Option<Box<IndexerProgress<'repo>>>,
441}
442
443pub struct OdbPackwriter<'repo> {
445 raw: *mut raw::git_odb_writepack,
446 progress: raw::git_indexer_progress,
447 progress_payload_ptr: *mut OdbPackwriterCb<'repo>,
448}
449
450impl<'repo> OdbPackwriter<'repo> {
451 pub fn commit(&mut self) -> Result<i32, Error> {
453 unsafe {
454 let writepack = &*self.raw;
455 let res = match writepack.commit {
456 Some(commit) => commit(self.raw, &mut self.progress),
457 None => -1,
458 };
459
460 if res < 0 {
461 Err(Error::last_error(res))
462 } else {
463 Ok(res)
464 }
465 }
466 }
467
468 pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo>
471 where
472 F: FnMut(Progress<'_>) -> bool + 'repo,
473 {
474 let progress_payload =
475 unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
476
477 progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>);
478 self
479 }
480}
481
482impl<'repo> io::Write for OdbPackwriter<'repo> {
483 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
484 unsafe {
485 let ptr = buf.as_ptr() as *mut c_void;
486 let len = buf.len();
487
488 let writepack = &*self.raw;
489 let res = match writepack.append {
490 Some(append) => append(self.raw, ptr, len, &mut self.progress),
491 None => -1,
492 };
493
494 if res < 0 {
495 Err(io::Error::new(io::ErrorKind::Other, "Write error"))
496 } else {
497 Ok(buf.len())
498 }
499 }
500 }
501 fn flush(&mut self) -> io::Result<()> {
502 Ok(())
503 }
504}
505
506impl<'repo> Drop for OdbPackwriter<'repo> {
507 fn drop(&mut self) {
508 unsafe {
509 let writepack = &*self.raw;
510 match writepack.free {
511 Some(free) => free(self.raw),
512 None => (),
513 };
514
515 drop(Box::from_raw(self.progress_payload_ptr));
516 }
517 }
518}
519
520pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
521
522struct ForeachCbData<'a> {
523 pub callback: &'a mut ForeachCb<'a>,
524}
525
526extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int {
527 panic::wrap(|| unsafe {
528 let data = &mut *(payload as *mut ForeachCbData<'_>);
529 let res = {
530 let callback = &mut data.callback;
531 callback(&Binding::from_raw(id))
532 };
533
534 if res {
535 0
536 } else {
537 1
538 }
539 })
540 .unwrap_or(1)
541}
542
543pub(crate) extern "C" fn write_pack_progress_cb(
544 stats: *const raw::git_indexer_progress,
545 payload: *mut c_void,
546) -> c_int {
547 let ok = panic::wrap(|| unsafe {
548 let payload = &mut *(payload as *mut OdbPackwriterCb<'_>);
549
550 let callback = match payload.cb {
551 Some(ref mut cb) => cb,
552 None => return true,
553 };
554
555 let progress: Progress<'_> = Binding::from_raw(stats);
556 callback(progress)
557 });
558 if ok == Some(true) {
559 0
560 } else {
561 -1
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use crate::{Buf, ObjectType, Oid, Repository};
568 use std::io::prelude::*;
569 use tempfile::TempDir;
570
571 #[test]
572 fn read() {
573 let td = TempDir::new().unwrap();
574 let repo = Repository::init(td.path()).unwrap();
575 let dat = [4, 3, 5, 6, 9];
576 let id = repo.blob(&dat).unwrap();
577 let db = repo.odb().unwrap();
578 let obj = db.read(id).unwrap();
579 let data = obj.data();
580 let size = obj.len();
581 assert_eq!(size, 5);
582 assert_eq!(dat, data);
583 assert_eq!(id, obj.id());
584 }
585
586 #[test]
587 fn read_header() {
588 let td = TempDir::new().unwrap();
589 let repo = Repository::init(td.path()).unwrap();
590 let dat = [4, 3, 5, 6, 9];
591 let id = repo.blob(&dat).unwrap();
592 let db = repo.odb().unwrap();
593 let (size, kind) = db.read_header(id).unwrap();
594
595 assert_eq!(size, 5);
596 assert_eq!(kind, ObjectType::Blob);
597 }
598
599 #[test]
600 fn write() {
601 let td = TempDir::new().unwrap();
602 let repo = Repository::init(td.path()).unwrap();
603 let dat = [4, 3, 5, 6, 9];
604 let db = repo.odb().unwrap();
605 let id = db.write(ObjectType::Blob, &dat).unwrap();
606 let blob = repo.find_blob(id).unwrap();
607 assert_eq!(blob.content(), dat);
608 }
609
610 #[test]
611 fn writer() {
612 let td = TempDir::new().unwrap();
613 let repo = Repository::init(td.path()).unwrap();
614 let dat = [4, 3, 5, 6, 9];
615 let db = repo.odb().unwrap();
616 let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap();
617 let wl = ws.write(&dat[0..3]).unwrap();
618 assert_eq!(wl, 3);
619 let wl = ws.write(&dat[3..5]).unwrap();
620 assert_eq!(wl, 2);
621 let id = ws.finalize().unwrap();
622 let blob = repo.find_blob(id).unwrap();
623 assert_eq!(blob.content(), dat);
624 }
625
626 #[test]
627 fn exists() {
628 let td = TempDir::new().unwrap();
629 let repo = Repository::init(td.path()).unwrap();
630 let dat = [4, 3, 5, 6, 9];
631 let db = repo.odb().unwrap();
632 let id = db.write(ObjectType::Blob, &dat).unwrap();
633 assert!(db.exists(id));
634 }
635
636 #[test]
637 fn exists_prefix() {
638 let td = TempDir::new().unwrap();
639 let repo = Repository::init(td.path()).unwrap();
640 let dat = [4, 3, 5, 6, 9];
641 let db = repo.odb().unwrap();
642 let id = db.write(ObjectType::Blob, &dat).unwrap();
643 let id_prefix_str = &id.to_string()[0..10];
644 let id_prefix = Oid::from_str(id_prefix_str).unwrap();
645 let found_oid = db.exists_prefix(id_prefix, 10).unwrap();
646 assert_eq!(found_oid, id);
647 }
648
649 #[test]
650 fn packwriter() {
651 let (_td, repo_source) = crate::test::repo_init();
652 let (_td, repo_target) = crate::test::repo_init();
653 let mut builder = t!(repo_source.packbuilder());
654 let mut buf = Buf::new();
655 let (commit_source_id, _tree) = crate::test::commit(&repo_source);
656 t!(builder.insert_object(commit_source_id, None));
657 t!(builder.write_buf(&mut buf));
658 let db = repo_target.odb().unwrap();
659 let mut packwriter = db.packwriter().unwrap();
660 packwriter.write(&buf).unwrap();
661 packwriter.commit().unwrap();
662 let commit_target = repo_target.find_commit(commit_source_id).unwrap();
663 assert_eq!(commit_target.id(), commit_source_id);
664 }
665
666 #[test]
667 fn packwriter_progress() {
668 let mut progress_called = false;
669 {
670 let (_td, repo_source) = crate::test::repo_init();
671 let (_td, repo_target) = crate::test::repo_init();
672 let mut builder = t!(repo_source.packbuilder());
673 let mut buf = Buf::new();
674 let (commit_source_id, _tree) = crate::test::commit(&repo_source);
675 t!(builder.insert_object(commit_source_id, None));
676 t!(builder.write_buf(&mut buf));
677 let db = repo_target.odb().unwrap();
678 let mut packwriter = db.packwriter().unwrap();
679 packwriter.progress(|_| {
680 progress_called = true;
681 true
682 });
683 packwriter.write(&buf).unwrap();
684 packwriter.commit().unwrap();
685 }
686 assert_eq!(progress_called, true);
687 }
688
689 #[test]
690 fn write_with_mempack() {
691 use crate::{Buf, ResetType};
692 use std::io::Write;
693 use std::path::Path;
694
695 let (_td, repo) = crate::test::repo_init();
697 let odb = repo.odb().unwrap();
698 let mempack = odb.add_new_mempack_backend(1000).unwrap();
699
700 let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
702 assert!(!foo_file.exists());
703
704 let (oid1, _id) = crate::test::commit(&repo);
707 let commit1 = repo.find_commit(oid1).unwrap();
708 t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
709 assert!(foo_file.exists());
710
711 let mut buf = Buf::new();
715 mempack.dump(&repo, &mut buf).unwrap();
716 mempack.reset().unwrap();
717 assert!(repo
718 .reset(commit1.as_object(), ResetType::Hard, None)
719 .is_err());
720
721 let mut packwriter = odb.packwriter().unwrap();
724 packwriter.write(&buf).unwrap();
725 packwriter.commit().unwrap();
726 t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
727 assert!(foo_file.exists());
728 }
729
730 #[test]
731 fn stream_read() {
732 const FOO_TEXT: &[u8] = b"this is a test";
734 let (_td, repo) = crate::test::repo_init();
735 let p = repo.path().parent().unwrap().join("foo");
736 std::fs::write(&p, FOO_TEXT).unwrap();
737 let mut index = repo.index().unwrap();
738 index.add_path(std::path::Path::new("foo")).unwrap();
739 let tree_id = index.write_tree().unwrap();
740 let tree = repo.find_tree(tree_id).unwrap();
741 let sig = repo.signature().unwrap();
742 let head_id = repo.refname_to_id("HEAD").unwrap();
743 let parent = repo.find_commit(head_id).unwrap();
744 let _commit = repo
745 .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
746 .unwrap();
747
748 let odb = repo.odb().unwrap();
750 let oid = repo.refname_to_id("HEAD").unwrap();
751 let (mut reader, size, ty) = odb.reader(oid).unwrap();
752 assert!(ty == ObjectType::Commit);
753 let mut x = [0; 10000];
754 let r = reader.read(&mut x).unwrap();
755 assert!(r == size);
756
757 let commit = repo.find_commit(oid).unwrap();
760 let tree = commit.tree().unwrap();
761 let entry = tree.get_name("foo").unwrap();
762 let (mut reader, size, ty) = odb.reader(entry.id()).unwrap();
763 assert_eq!(size, FOO_TEXT.len());
764 assert!(ty == ObjectType::Blob);
765 let mut x = [0; 10000];
766 let r = reader.read(&mut x).unwrap();
767 assert_eq!(r, 14);
768 assert_eq!(&x[..FOO_TEXT.len()], FOO_TEXT);
769 }
770}