1mod composite;
11mod files;
12#[cfg(test)]
13pub(crate) mod flaky;
14pub mod loose;
15mod midx;
16mod packs;
17
18pub use composite::CompositeStore;
19pub use files::FilesSource;
20pub use loose::LooseStore;
21pub(crate) use loose::{LooseNegativeCache, SharedLooseNegativeCache};
22pub use midx::{MidxObjects, MidxObjectsStatus};
23pub(crate) use packs::touch_pack_mtime;
24pub use packs::{PackFilter, PackedObjects};
25
26use std::io::{self, Cursor, Read};
27use std::ops::ControlFlow;
28use std::sync::{Arc, RwLock};
29
30use std::path::Path;
31
32use crate::error::{Error, Result};
33use crate::hash;
34use crate::objects::{
35 oid_hash_map, HashAlgo, Object, ObjectId, ObjectIdPrefix, ObjectInfo, ObjectKind, OidHashMap,
36};
37use crate::odb::WriteOptions;
38use crate::pack;
39
40type MemoryObjectEntry = (ObjectKind, Arc<[u8]>);
41
42#[derive(Debug)]
44pub(crate) enum BoundedRead {
45 Object(Object),
47 Larger,
49}
50
51#[derive(Debug)]
53pub(crate) enum LocalSizedRead {
54 Packed(Option<u64>),
57 Loose(BoundedRead),
59 Missing,
61}
62
63pub struct ObjectStream<'a> {
67 pub kind: ObjectKind,
69 pub size: u64,
71 pub reader: Box<dyn Read + Send + 'a>,
73}
74
75pub trait ObjectStore: Send + Sync + std::fmt::Debug {
86 fn hash_algo(&self) -> HashAlgo;
88
89 fn read(&self, oid: &ObjectId) -> Result<Option<Object>>;
95
96 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
108 let Some(obj) = self.read(oid)? else {
109 return Ok(None);
110 };
111 buf.clear();
112 buf.extend_from_slice(&obj.data);
113 Ok(Some(obj.kind))
114 }
115
116 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>>;
122
123 fn contains(&self, oid: &ObjectId) -> Result<bool> {
129 Ok(self.read_info(oid)?.is_some())
130 }
131
132 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
145 oids.iter().map(|oid| self.contains(oid)).collect()
146 }
147
148 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
156 let Some(obj) = self.read(oid)? else {
157 return Ok(None);
158 };
159 let size = u64::try_from(obj.data.len()).map_err(|_| {
160 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
161 })?;
162 Ok(Some(ObjectStream {
163 kind: obj.kind,
164 size,
165 reader: Box::new(Cursor::new(obj.data)),
166 }))
167 }
168
169 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()>;
175
176 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
194 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
195 let mut count = 0usize;
196 self.for_each_object(&mut |oid| {
197 if prefix.matches(oid) {
198 out.push(*oid);
199 count += 1;
200 if limit != 0 && count >= limit {
201 return ControlFlow::Break(());
202 }
203 }
204 ControlFlow::Continue(())
205 })
206 }
207
208 fn refresh(&self) -> Result<bool> {
216 Ok(false)
217 }
218}
219
220pub trait WritableObjectStore: ObjectStore {
222 fn write(&self, kind: ObjectKind, data: &[u8], options: WriteOptions) -> Result<ObjectId>;
231
232 fn write_stream(
240 &self,
241 kind: ObjectKind,
242 size: u64,
243 reader: &mut dyn Read,
244 options: WriteOptions,
245 ) -> Result<ObjectId> {
246 let size_usize = usize::try_from(size).map_err(|_| {
247 Error::CorruptObject(format!("object size {size} exceeds address space"))
248 })?;
249 let mut buf = vec![0u8; size_usize];
250 reader.read_exact(&mut buf).map_err(Error::Io)?;
251 self.write(kind, &buf, options)
252 }
253
254 fn freshen(&self, oid: &ObjectId) -> Result<bool>;
262
263 fn flush(&self) -> Result<()> {
276 Ok(())
277 }
278
279 fn flush_for_publish(&self) -> Result<()> {
285 if let Err(err) = self.flush() {
286 let _ = self.rollback();
287 return Err(err);
288 }
289 Ok(())
290 }
291
292 fn rollback(&self) -> Result<()> {
300 Ok(())
301 }
302}
303
304macro_rules! forward_object_store {
309 ($pointer:ty) => {
310 impl<T: ObjectStore + ?Sized> ObjectStore for $pointer {
311 fn hash_algo(&self) -> HashAlgo {
312 (**self).hash_algo()
313 }
314
315 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
316 (**self).read(oid)
317 }
318
319 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
320 (**self).read_into(oid, buf)
321 }
322
323 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
324 (**self).read_info(oid)
325 }
326
327 fn contains(&self, oid: &ObjectId) -> Result<bool> {
328 (**self).contains(oid)
329 }
330
331 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
332 (**self).contains_many(oids)
333 }
334
335 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
336 (**self).open_stream(oid)
337 }
338
339 fn for_each_object(
340 &self,
341 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
342 ) -> Result<()> {
343 (**self).for_each_object(f)
344 }
345
346 fn lookup_prefix(
347 &self,
348 prefix: &str,
349 limit: usize,
350 out: &mut Vec<ObjectId>,
351 ) -> Result<()> {
352 (**self).lookup_prefix(prefix, limit, out)
353 }
354
355 fn refresh(&self) -> Result<bool> {
356 (**self).refresh()
357 }
358 }
359
360 impl<T: WritableObjectStore + ?Sized> WritableObjectStore for $pointer {
361 fn write(
362 &self,
363 kind: ObjectKind,
364 data: &[u8],
365 options: WriteOptions,
366 ) -> Result<ObjectId> {
367 (**self).write(kind, data, options)
368 }
369
370 fn write_stream(
371 &self,
372 kind: ObjectKind,
373 size: u64,
374 reader: &mut dyn Read,
375 options: WriteOptions,
376 ) -> Result<ObjectId> {
377 (**self).write_stream(kind, size, reader, options)
378 }
379
380 fn freshen(&self, oid: &ObjectId) -> Result<bool> {
381 (**self).freshen(oid)
382 }
383
384 fn flush(&self) -> Result<()> {
385 (**self).flush()
386 }
387
388 fn flush_for_publish(&self) -> Result<()> {
389 (**self).flush_for_publish()
390 }
391
392 fn rollback(&self) -> Result<()> {
393 (**self).rollback()
394 }
395 }
396 };
397}
398
399forward_object_store!(Arc<T>);
400forward_object_store!(Box<T>);
401forward_object_store!(&T);
402
403pub(crate) fn local_object_materialized(objects_dir: &Path, oid: &ObjectId) -> bool {
405 if local_object_materialized_without_rescan(objects_dir, oid) {
406 return true;
407 }
408 if pack::reprepare_pack_directory_on_miss(objects_dir).ok() == Some(true) {
409 return object_in_local_packs(objects_dir, oid);
410 }
411 false
412}
413
414pub(crate) fn local_object_materialized_without_rescan(objects_dir: &Path, oid: &ObjectId) -> bool {
417 object_in_local_packs(objects_dir, oid) || oid.loose_path_in(objects_dir).is_file()
418}
419
420fn object_in_local_packs(objects_dir: &Path, oid: &ObjectId) -> bool {
421 object_in_non_promisor_pack(objects_dir, oid)
422}
423
424pub(crate) fn object_in_non_promisor_pack(objects_dir: &Path, oid: &ObjectId) -> bool {
426 let Ok(indexes) = pack::read_local_pack_indexes_cached(objects_dir) else {
427 return false;
428 };
429 for idx in &indexes {
430 if idx.is_promisor {
431 continue;
432 }
433 if idx.contains(oid).unwrap_or(false) {
434 return true;
435 }
436 }
437 false
438}
439
440pub(crate) fn local_object_stored_without_rescan(objects_dir: &Path, oid: &ObjectId) -> bool {
445 let Ok(indexes) = pack::read_local_pack_indexes_cached(objects_dir) else {
446 return oid.loose_path_in(objects_dir).is_file();
447 };
448 for idx in &indexes {
449 if idx.contains(oid).unwrap_or(false) {
450 return true;
451 }
452 }
453 oid.loose_path_in(objects_dir).is_file()
454}
455
456pub(crate) fn local_object_stored(objects_dir: &Path, oid: &ObjectId) -> bool {
458 if local_object_stored_without_rescan(objects_dir, oid) {
459 return true;
460 }
461 if pack::reprepare_pack_directory_on_miss(objects_dir).ok() == Some(true) {
462 return local_object_stored_without_rescan(objects_dir, oid);
463 }
464 false
465}
466
467#[derive(Debug)]
471pub struct MemoryStore {
472 algo: HashAlgo,
473 objects: RwLock<OidHashMap<MemoryObjectEntry>>,
474}
475
476impl MemoryStore {
477 #[must_use]
479 pub fn new(algo: HashAlgo) -> Self {
480 Self {
481 algo,
482 objects: RwLock::new(oid_hash_map()),
483 }
484 }
485
486 pub(crate) fn insert_object(&self, oid: ObjectId, kind: ObjectKind, data: Arc<[u8]>) {
488 if let Ok(mut guard) = self.objects.write() {
489 guard.entry(oid).or_insert((kind, data));
490 }
491 }
492}
493
494impl ObjectStore for MemoryStore {
495 fn hash_algo(&self) -> HashAlgo {
496 self.algo
497 }
498
499 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
500 let guard = self
501 .objects
502 .read()
503 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
504 Ok(guard.get(oid).map(|(kind, data)| Object {
505 kind: *kind,
506 data: data.to_vec(),
507 }))
508 }
509
510 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
511 let guard = self
512 .objects
513 .read()
514 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
515 Ok(guard.get(oid).map(|(kind, data)| {
516 buf.clear();
517 buf.extend_from_slice(data);
518 *kind
519 }))
520 }
521
522 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
523 let guard = self
524 .objects
525 .read()
526 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
527 Ok(guard.get(oid).map(|(kind, data)| ObjectInfo {
528 kind: *kind,
529 size: u64::try_from(data.len()).unwrap_or(u64::MAX),
530 }))
531 }
532
533 fn contains(&self, oid: &ObjectId) -> Result<bool> {
534 let guard = self
535 .objects
536 .read()
537 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
538 Ok(guard.contains_key(oid))
539 }
540
541 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
542 let guard = self
543 .objects
544 .read()
545 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
546 Ok(oids.iter().map(|oid| guard.contains_key(oid)).collect())
547 }
548
549 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
550 let guard = self
551 .objects
552 .read()
553 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
554 let Some((kind, data)) = guard.get(oid) else {
555 return Ok(None);
556 };
557 let size = u64::try_from(data.len()).map_err(|_| {
558 Error::CorruptObject(format!("object size overflow for {}", oid.to_hex()))
559 })?;
560 Ok(Some(ObjectStream {
561 kind: *kind,
562 size,
563 reader: Box::new(Cursor::new(Arc::clone(data))),
564 }))
565 }
566
567 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
568 let guard = self
569 .objects
570 .read()
571 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
572 for oid in guard.keys() {
573 if f(oid).is_break() {
574 break;
575 }
576 }
577 Ok(())
578 }
579
580 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
581 let prefix = ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
582 let guard = self
583 .objects
584 .read()
585 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
586 let matches = guard.keys().filter(|oid| prefix.matches(oid)).copied();
587 if limit == 0 {
588 out.extend(matches);
589 } else {
590 out.extend(matches.take(limit));
591 }
592 Ok(())
593 }
594}
595
596impl WritableObjectStore for MemoryStore {
597 fn write(&self, kind: ObjectKind, data: &[u8], _options: WriteOptions) -> Result<ObjectId> {
598 let oid = hash::hash_object(self.algo, kind, data)?;
599 let mut guard = self
600 .objects
601 .write()
602 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
603 guard
604 .entry(oid)
605 .or_insert_with(|| (kind, Arc::from(data.to_owned())));
606 Ok(oid)
607 }
608
609 fn freshen(&self, oid: &ObjectId) -> Result<bool> {
610 let guard = self
611 .objects
612 .read()
613 .map_err(|e| Error::Io(io::Error::other(e.to_string())))?;
614 Ok(guard.contains_key(oid))
615 }
616}
617
618pub(crate) fn resolve_pending(
623 oids: &[ObjectId],
624 present: &mut [bool],
625 pending: &mut Vec<usize>,
626 probe: impl FnOnce(&[ObjectId]) -> Result<Vec<bool>>,
627) -> Result<()> {
628 if pending.is_empty() {
629 return Ok(());
630 }
631 let batch: Vec<ObjectId> = pending.iter().map(|&i| oids[i]).collect();
632 let mut found = probe(&batch)?.into_iter();
633 pending.retain(|&i| {
634 let hit = found.next().unwrap_or(false);
635 present[i] |= hit;
636 !hit
637 });
638 Ok(())
639}
640
641pub(crate) fn for_each_propagate_break(
643 store: &dyn ObjectStore,
644 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
645) -> Result<bool> {
646 let mut stop = false;
647 store.for_each_object(&mut |oid| {
648 if stop {
649 return ControlFlow::Break(());
650 }
651 match f(oid) {
652 ControlFlow::Break(()) => {
653 stop = true;
654 ControlFlow::Break(())
655 }
656 ControlFlow::Continue(()) => ControlFlow::Continue(()),
657 }
658 })?;
659 Ok(stop)
660}
661
662#[cfg(test)]
663mod tests {
664 use super::*;
665 use std::io::Read;
666
667 #[test]
668 fn read_hit_and_miss() {
669 let store = MemoryStore::new(HashAlgo::Sha1);
670 let data = b"hello";
671 let oid = store
672 .write(ObjectKind::Blob, data, WriteOptions::default())
673 .unwrap();
674 let obj = store.read(&oid).unwrap().expect("hit");
675 assert_eq!(obj.kind, ObjectKind::Blob);
676 assert_eq!(obj.data, data);
677 assert!(store.read(&ObjectId::zero()).unwrap().is_none());
678 let mut buf = b"stale bytes".to_vec();
679 assert_eq!(
680 store.read_into(&oid, &mut buf).unwrap(),
681 Some(ObjectKind::Blob)
682 );
683 assert_eq!(buf, data);
684 assert_eq!(store.read_into(&ObjectId::zero(), &mut buf).unwrap(), None);
685 assert_eq!(buf, data, "a miss leaves the buffer alone");
686 assert!(store.read_info(&ObjectId::zero()).unwrap().is_none());
687 assert!(!store.contains(&ObjectId::zero()).unwrap());
688 assert!(store.contains(&oid).unwrap());
689 assert_eq!(
690 store.contains_many(&[oid, ObjectId::zero(), oid]).unwrap(),
691 vec![true, false, true]
692 );
693 assert!(store.contains_many(&[]).unwrap().is_empty());
694 }
695
696 #[test]
697 fn resolve_pending_marks_hits_and_keeps_misses() {
698 let ids: Vec<ObjectId> = (1u8..=3)
699 .map(|b| ObjectId::from_bytes(&[b; 20]).unwrap())
700 .collect();
701 let mut present = vec![false; 3];
702 let mut pending = vec![0, 2];
703 resolve_pending(&ids, &mut present, &mut pending, |batch| {
704 assert_eq!(batch, [ids[0], ids[2]]);
705 Ok(vec![false, true])
706 })
707 .unwrap();
708 assert_eq!(present, [false, false, true]);
709 assert_eq!(pending, [0]);
710
711 resolve_pending(&ids, &mut present, &mut pending, |_| Ok(Vec::new())).unwrap();
712 assert_eq!(pending, [0], "a short answer counts as absent");
713
714 pending.clear();
715 resolve_pending(&ids, &mut present, &mut pending, |_| {
716 panic!("no probe when nothing is pending")
717 })
718 .unwrap();
719 }
720
721 #[test]
722 fn stream_four_mib_blob_in_sixty_four_kib_chunks() {
723 let store = MemoryStore::new(HashAlgo::Sha1);
724 let payload = vec![0xABu8; 4 * 1024 * 1024];
725 let oid = store
726 .write(ObjectKind::Blob, &payload, WriteOptions::default())
727 .unwrap();
728 let mut stream = store.open_stream(&oid).unwrap().expect("stream");
729 assert_eq!(stream.kind, ObjectKind::Blob);
730 assert_eq!(stream.size, payload.len() as u64);
731 let mut out = Vec::with_capacity(payload.len());
732 let mut chunk = [0u8; 64 * 1024];
733 loop {
734 let n = stream.reader.read(&mut chunk).unwrap();
735 if n == 0 {
736 break;
737 }
738 out.extend_from_slice(&chunk[..n]);
739 }
740 assert_eq!(out, payload);
741 }
742
743 #[test]
744 fn for_each_object_stops_on_break() {
745 let store = MemoryStore::new(HashAlgo::Sha1);
746 for i in 0u8..5 {
747 store
748 .write(ObjectKind::Blob, &[i], WriteOptions::default())
749 .unwrap();
750 }
751 let mut seen = 0usize;
752 store
753 .for_each_object(&mut |_| {
754 seen += 1;
755 ControlFlow::Break(())
756 })
757 .unwrap();
758 assert_eq!(seen, 1);
759 }
760
761 #[test]
762 fn lookup_prefix_unique_ambiguous_and_none() {
763 let store = MemoryStore::new(HashAlgo::Sha1);
764 let a = store
765 .write(ObjectKind::Blob, b"a", WriteOptions::default())
766 .unwrap();
767 let b = store
768 .write(ObjectKind::Blob, b"b", WriteOptions::default())
769 .unwrap();
770 let hex_a = a.to_hex();
771 let unique_prefix = &hex_a[..12];
772 let mut out = Vec::new();
773 store.lookup_prefix(unique_prefix, 0, &mut out).unwrap();
774 assert_eq!(out, vec![a]);
775
776 let mut ambiguous = Vec::new();
777 store.lookup_prefix("", 2, &mut ambiguous).unwrap();
778 assert_eq!(ambiguous.len(), 2);
779 assert!(ambiguous.contains(&a));
780 assert!(ambiguous.contains(&b));
781
782 let mut none = Vec::new();
783 store
784 .lookup_prefix("ffffffffffffffffffffffffffffffffffffffff", 0, &mut none)
785 .unwrap();
786 assert!(none.is_empty());
787 }
788
789 #[test]
790 fn lookup_prefix_odd_even_empty_and_full_length() {
791 let store = MemoryStore::new(HashAlgo::Sha1);
792 let ids: Vec<ObjectId> = [
793 "abcdef0123000000000000000000000000000000",
794 "abcdef0124000000000000000000000000000000",
795 "abd0000000000000000000000000000000000000",
796 ]
797 .iter()
798 .map(|hex| ObjectId::from_hex(hex).unwrap())
799 .collect();
800 for id in &ids {
801 store.insert_object(*id, ObjectKind::Blob, Arc::from(Vec::new()));
802 }
803 let lookup = |prefix: &str, limit: usize| {
804 let mut out = Vec::new();
805 store.lookup_prefix(prefix, limit, &mut out).unwrap();
806 out.sort();
807 out
808 };
809 assert_eq!(lookup("", 0), ids);
810 assert_eq!(lookup("ab", 1).len(), 1);
811 assert_eq!(lookup("abc", 0), ids[..2]);
812 assert_eq!(lookup("abcdef012", 0), ids[..2]);
813 assert_eq!(lookup("ABCDEF0124", 0), ids[1..2]);
814 assert_eq!(lookup(&ids[2].to_hex(), 0), ids[2..]);
815 assert!(lookup("abe", 0).is_empty());
816 let mut out = Vec::new();
817 assert!(matches!(
818 store.lookup_prefix("xyz", 0, &mut out),
819 Err(Error::InvalidObjectId(_))
820 ));
821 }
822
823 #[test]
824 fn write_idempotent() {
825 let store = MemoryStore::new(HashAlgo::Sha1);
826 let data = b"same";
827 let oid1 = store
828 .write(ObjectKind::Blob, data, WriteOptions::default())
829 .unwrap();
830 let oid2 = store
831 .write(ObjectKind::Blob, data, WriteOptions::default())
832 .unwrap();
833 assert_eq!(oid1, oid2);
834 let guard = store.objects.read().unwrap();
835 assert_eq!(guard.len(), 1);
836 }
837
838 #[test]
839 fn write_stream_default_and_freshen() {
840 let store = MemoryStore::new(HashAlgo::Sha1);
841 let payload = b"streamed";
842 let mut cursor = Cursor::new(*payload);
843 let oid = store
844 .write_stream(
845 ObjectKind::Blob,
846 payload.len() as u64,
847 &mut cursor,
848 WriteOptions::default(),
849 )
850 .unwrap();
851 assert_eq!(
852 store.read(&oid).unwrap().expect("written").data,
853 payload.as_slice()
854 );
855 assert!(store.freshen(&oid).unwrap());
856 assert!(!store.freshen(&ObjectId::zero()).unwrap());
857 }
858
859 #[test]
860 fn refresh_default_false() {
861 let store = MemoryStore::new(HashAlgo::Sha1);
862 assert!(!store.refresh().unwrap());
863 }
864
865 #[test]
866 fn trait_defaults_via_blanket_test_store() {
867 struct DefaultsOnly {
868 inner: MemoryStore,
869 }
870
871 impl std::fmt::Debug for DefaultsOnly {
872 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
873 self.inner.fmt(f)
874 }
875 }
876
877 impl ObjectStore for DefaultsOnly {
878 fn hash_algo(&self) -> HashAlgo {
879 self.inner.hash_algo()
880 }
881
882 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
883 self.inner.read(oid)
884 }
885
886 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
887 self.inner.read_info(oid)
888 }
889
890 fn for_each_object(
891 &self,
892 f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>,
893 ) -> Result<()> {
894 self.inner.for_each_object(f)
895 }
896 }
897
898 let store = DefaultsOnly {
899 inner: MemoryStore::new(HashAlgo::Sha1),
900 };
901 let oid = WritableObjectStore::write(
902 &store.inner,
903 ObjectKind::Blob,
904 b"x",
905 WriteOptions::default(),
906 )
907 .unwrap();
908 assert!(ObjectStore::contains(&store, &oid).unwrap());
909 assert_eq!(
910 ObjectStore::contains_many(&store, &[ObjectId::zero(), oid]).unwrap(),
911 vec![false, true]
912 );
913 assert!(ObjectStore::open_stream(&store, &oid).unwrap().is_some());
914 let mut prefixed = Vec::new();
915 ObjectStore::lookup_prefix(&store, &oid.to_hex()[..6], 0, &mut prefixed).unwrap();
916 assert_eq!(prefixed, vec![oid]);
917 }
918}