1use parking_lot::RwLock;
22use sley_core::{GitError, MissingObjectContext, ObjectFormat, ObjectId, Result};
23use sley_object::{Commit, EncodedObject, ObjectType, Tag, TreeEntries};
24use sley_pack::{
25 MultiPackIndex, MultiPackIndexOidLookup, PackIndex, PackIndexByteSource, PackIndexViewData,
26 PackReverseIndex,
27};
28use std::collections::{HashMap, HashSet};
29use std::path::{Path, PathBuf};
30use std::sync::Mutex;
31use std::sync::{Arc, OnceLock};
32use std::{env, fs};
33
34use crate::{ObjectReader, ObjectWriter, implied_empty_tree_object};
35
36use crate::install::{ObjectPrefixResolution, ObjectStorageInfo};
37use crate::loose::LooseObjectStore;
38use crate::reachability::{
39 PackDeltaBase, PackIndexOffsetInfo, pack_entry_delta_base, scan_pack_index_offsets,
40 scan_pack_offsets_without_index, zero_oid,
41};
42use crate::registry::{
43 ObjectPresenceChecker, PackLookup, PackRegistryCache, PackRegistrySnapshot,
44 alternate_object_dirs, object_ids_in_objects_dir, object_ids_with_prefix_in_objects_dir,
45 read_incremental_midx_chain, repository_objects_dir, same_registered_pack_set,
46 scan_pack_registry, validate_object_id_prefix,
47};
48
49pub struct ObjectDatabase {
50 pub(crate) format: ObjectFormat,
51 pub(crate) objects: Mutex<HashMap<ObjectId, Arc<EncodedObject>>>,
57 pub(crate) promisor: bool,
58}
59
60impl ObjectDatabase {
61 pub fn new(format: ObjectFormat) -> Self {
62 Self {
63 format,
64 objects: Mutex::new(HashMap::new()),
65 promisor: false,
66 }
67 }
68
69 pub fn with_promisor(mut self, promisor: bool) -> Self {
70 self.promisor = promisor;
71 self
72 }
73
74 pub fn contains(&self, oid: &ObjectId) -> bool {
75 self.objects
76 .lock()
77 .map(|objects| objects.contains_key(oid))
78 .unwrap_or(false)
79 }
80
81 pub fn validate(&self, oid: &ObjectId) -> Result<()> {
82 let object = self.read_object(oid)?;
83 let actual = object.object_id(self.format)?;
84 if &actual == oid {
85 Ok(())
86 } else {
87 Err(GitError::InvalidObject(format!(
88 "object id mismatch: expected {oid}, got {actual}"
89 )))
90 }
91 }
92}
93
94impl ObjectReader for ObjectDatabase {
95 fn read_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
96 self.objects
97 .lock()
98 .map_err(|_| GitError::object_not_found_in(*oid, MissingObjectContext::Read))?
99 .get(oid)
100 .map(Arc::clone)
101 .or_else(|| implied_empty_tree_object(self.format, oid))
102 .ok_or_else(|| GitError::object_not_found_in(*oid, MissingObjectContext::Read))
103 }
104}
105
106impl ObjectWriter for ObjectDatabase {
107 fn write_object(&self, object: EncodedObject) -> Result<ObjectId> {
108 let oid = object.object_id(self.format)?;
109 self.objects
110 .lock()
111 .map_err(|_| GitError::Io("object cache lock poisoned".into()))?
112 .entry(oid)
113 .or_insert_with(|| Arc::new(object));
114 Ok(oid)
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct Alternate {
120 pub path: std::path::PathBuf,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct PartialClonePolicy {
125 pub promisor_remote: Option<String>,
126 pub allow_missing_promised_objects: bool,
127}
128
129pub(crate) type PackBytesCache = Arc<RwLock<HashMap<PathBuf, Arc<PackData>>>>;
133
134#[derive(Debug)]
137pub(crate) enum PackData {
138 #[cfg(feature = "mmap")]
139 Mapped(sley_mmap::MappedFile),
140 Heap(Vec<u8>),
141}
142
143impl std::ops::Deref for PackData {
144 type Target = [u8];
145
146 fn deref(&self) -> &[u8] {
147 match self {
148 #[cfg(feature = "mmap")]
149 Self::Mapped(mapped) => mapped,
150 Self::Heap(bytes) => bytes,
151 }
152 }
153}
154
155#[cfg(feature = "mmap")]
158pub(crate) fn load_pack_data(pack_path: &Path) -> Result<PackData> {
159 match sley_mmap::MappedFile::open_pack(pack_path) {
160 Ok(mapped) => Ok(PackData::Mapped(mapped)),
161 Err(_) => Ok(PackData::Heap(fs::read(pack_path)?)),
162 }
163}
164
165#[cfg(not(feature = "mmap"))]
166pub(crate) fn load_pack_data(pack_path: &Path) -> Result<PackData> {
167 Ok(PackData::Heap(fs::read(pack_path)?))
168}
169
170#[cfg(feature = "mmap")]
171pub(crate) fn load_pack_index_data(index_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
172 match sley_mmap::MappedFile::open_pack(index_path) {
173 Ok(mapped) => Ok(Arc::new(mapped)),
174 Err(_) => Ok(Arc::new(fs::read(index_path)?)),
175 }
176}
177
178#[cfg(not(feature = "mmap"))]
179pub(crate) fn load_pack_index_data(index_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
180 Ok(Arc::new(fs::read(index_path)?))
181}
182
183#[cfg(feature = "mmap")]
184fn load_multi_pack_index_lookup_data(midx_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
185 match sley_mmap::MappedFile::open_multi_pack_index(midx_path) {
186 Ok(mapped) => Ok(Arc::new(mapped)),
187 Err(_) => Ok(Arc::new(fs::read(midx_path)?)),
188 }
189}
190
191#[cfg(not(feature = "mmap"))]
192fn load_multi_pack_index_lookup_data(midx_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
193 Ok(Arc::new(fs::read(midx_path)?))
194}
195
196pub(crate) type DecodedObjectCache = Arc<Mutex<LruObjectCache>>;
202
203pub(crate) type PackDeltaCaches = Arc<Mutex<HashMap<PathBuf, Arc<Mutex<LruOffsetCache>>>>>;
208
209pub(crate) type PackHeaderTypeCache = Arc<Mutex<HashMap<u64, (ObjectType, u64)>>>;
219
220pub(crate) type PackHeaderTypeCaches = Arc<Mutex<HashMap<PathBuf, PackHeaderTypeCache>>>;
221
222const DEFAULT_OBJECT_CACHE_BYTES: usize = 96 * 1024 * 1024;
228
229const DEFAULT_DELTA_BASE_CACHE_BYTES: usize = 96 * 1024 * 1024;
233
234pub(crate) fn cached_object_cost(object: &EncodedObject) -> usize {
238 object.body.len().saturating_add(64)
239}
240
241fn cache_budget_from_env(var: &str, default: usize) -> usize {
244 match env::var(var) {
245 Ok(value) => value.trim().parse::<usize>().unwrap_or(default),
246 Err(_) => default,
247 }
248}
249
250pub(crate) fn object_cache_budget() -> usize {
257 static BUDGET: OnceLock<usize> = OnceLock::new();
258 *BUDGET.get_or_init(|| {
259 cache_budget_from_env("SLEY_OBJECT_CACHE_BYTES", DEFAULT_OBJECT_CACHE_BYTES)
260 })
261}
262
263pub(crate) fn delta_base_cache_budget() -> usize {
267 static BUDGET: OnceLock<usize> = OnceLock::new();
268 *BUDGET.get_or_init(|| {
269 cache_budget_from_env(
270 "SLEY_DELTA_BASE_CACHE_BYTES",
271 DEFAULT_DELTA_BASE_CACHE_BYTES,
272 )
273 })
274}
275
276pub(crate) fn verify_reads_enabled() -> bool {
287 static VERIFY: OnceLock<bool> = OnceLock::new();
288 *VERIFY.get_or_init(|| match env::var("SLEY_VERIFY_READS") {
289 Ok(value) => !matches!(value.trim(), "" | "0"),
290 Err(_) => false,
291 })
292}
293
294#[derive(Debug)]
302pub(crate) struct LruCache<K: std::hash::Hash + Eq + Clone> {
303 budget: usize,
304 used: usize,
305 map: HashMap<K, LruEntry<K>>,
306 head: Option<K>,
307 tail: Option<K>,
308}
309
310#[derive(Debug)]
311struct LruEntry<K> {
312 object: Arc<EncodedObject>,
313 prev: Option<K>,
314 next: Option<K>,
315}
316
317impl<K: std::hash::Hash + Eq + Clone> LruCache<K> {
318 pub(crate) fn new(budget: usize) -> Self {
319 Self {
320 budget,
321 used: 0,
322 map: HashMap::new(),
323 head: None,
324 tail: None,
325 }
326 }
327
328 pub(crate) fn get(&mut self, key: &K) -> Option<Arc<EncodedObject>> {
329 let object = Arc::clone(&self.map.get(key)?.object);
330 self.touch(key);
331 Some(object)
332 }
333
334 pub(crate) fn touch(&mut self, key: &K) {
336 if self.tail.as_ref() == Some(key) {
337 return;
338 }
339 if self.map.contains_key(key) {
340 self.detach(key);
341 self.attach_back(key.clone());
342 }
343 }
344
345 pub(crate) fn remove(&mut self, key: &K) {
347 if let Some(entry) = self.map.get(key) {
348 self.used = self.used.saturating_sub(cached_object_cost(&entry.object));
349 }
350 self.detach(key);
351 self.map.remove(key);
352 }
353
354 pub(crate) fn detach(&mut self, key: &K) {
355 let Some((prev, next)) = self.map.get_mut(key).map(|entry| {
356 let prev = entry.prev.take();
357 let next = entry.next.take();
358 (prev, next)
359 }) else {
360 return;
361 };
362
363 match &prev {
364 Some(prev_key) => {
365 if let Some(prev_entry) = self.map.get_mut(prev_key) {
366 prev_entry.next = next.clone();
367 }
368 }
369 None => self.head = next.clone(),
370 }
371 match &next {
372 Some(next_key) => {
373 if let Some(next_entry) = self.map.get_mut(next_key) {
374 next_entry.prev = prev.clone();
375 }
376 }
377 None => self.tail = prev.clone(),
378 }
379 }
380
381 pub(crate) fn attach_back(&mut self, key: K) {
382 let previous_tail = self.tail.replace(key.clone());
383 match previous_tail {
384 Some(tail_key) => {
385 if let Some(tail_entry) = self.map.get_mut(&tail_key) {
386 tail_entry.next = Some(key.clone());
387 }
388 if let Some(entry) = self.map.get_mut(&key) {
389 entry.prev = Some(tail_key);
390 entry.next = None;
391 }
392 }
393 None => {
394 self.head = Some(key.clone());
395 if let Some(entry) = self.map.get_mut(&key) {
396 entry.prev = None;
397 entry.next = None;
398 }
399 }
400 }
401 }
402
403 pub(crate) fn clear(&mut self) {
404 self.map.clear();
405 self.head = None;
406 self.tail = None;
407 self.used = 0;
408 }
409
410 pub(crate) fn put(&mut self, key: K, object: Arc<EncodedObject>) {
411 if self.budget == 0 {
412 return;
413 }
414 let cost = cached_object_cost(&object);
415 if cost > self.budget {
419 self.remove(&key);
420 return;
421 }
422 if let Some(entry) = self.map.get_mut(&key) {
423 let previous = std::mem::replace(&mut entry.object, object);
424 self.used = self
426 .used
427 .saturating_sub(cached_object_cost(&previous))
428 .saturating_add(cost);
429 self.touch(&key);
430 } else {
431 self.used = self.used.saturating_add(cost);
432 self.map.insert(
433 key.clone(),
434 LruEntry {
435 object,
436 prev: None,
437 next: None,
438 },
439 );
440 self.attach_back(key);
441 }
442 while self.used > self.budget {
443 let Some(evicted) = self.head.clone() else {
444 break;
445 };
446 self.remove(&evicted);
447 }
448 }
449}
450
451type LruObjectCache = LruCache<ObjectId>;
453pub(crate) type LruOffsetCache = LruCache<u64>;
455
456struct PackDeltaCacheAdapter<'a>(&'a Arc<Mutex<LruOffsetCache>>);
461
462impl sley_pack::PackDeltaCache for PackDeltaCacheAdapter<'_> {
463 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
464 self.0.lock().ok()?.get(&offset)
465 }
466
467 fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
468 if let Ok(mut cache) = self.0.lock() {
469 cache.put(offset, object);
470 }
471 }
472}
473
474struct PackHeaderTypeCacheAdapter<'a>(&'a PackHeaderTypeCache);
478
479impl sley_pack::HeaderTypeCache for PackHeaderTypeCacheAdapter<'_> {
480 fn get(&self, pack_offset: u64) -> Option<(ObjectType, u64)> {
481 self.0.lock().ok()?.get(&pack_offset).copied()
482 }
483
484 fn put(&mut self, pack_offset: u64, header: (ObjectType, u64)) {
485 if let Ok(mut cache) = self.0.lock() {
486 cache.insert(pack_offset, header);
487 }
488 }
489}
490
491pub(crate) type PackIndexCache = Arc<RwLock<HashMap<PathBuf, Arc<PackIndexViewData>>>>;
496
497pub(crate) type PackReverseIndexCache =
500 Arc<RwLock<HashMap<PathBuf, Option<Arc<PackReverseIndex>>>>>;
501
502pub(crate) type MultiPackIndexCache = Arc<RwLock<HashMap<PathBuf, Arc<MultiPackIndex>>>>;
506
507#[derive(Debug, Clone, Default, PartialEq, Eq)]
513pub struct ObjectReplacements {
514 replacements: HashMap<ObjectId, ObjectId>,
515}
516
517impl ObjectReplacements {
518 pub fn new(replacements: impl IntoIterator<Item = (ObjectId, ObjectId)>) -> Self {
519 Self {
520 replacements: replacements.into_iter().collect(),
521 }
522 }
523
524 pub fn is_empty(&self) -> bool {
525 self.replacements.is_empty()
526 }
527
528 pub fn resolve(&self, oid: &ObjectId) -> Result<ObjectId> {
531 let mut current = *oid;
532 let mut seen = HashSet::new();
533 for _ in 0..5 {
534 if !seen.insert(current) {
535 return Err(GitError::InvalidObject(format!(
536 "replace depth too high for object {oid}"
537 )));
538 }
539 let Some(next) = self.replacements.get(¤t) else {
540 return Ok(current);
541 };
542 if *next == current {
546 return Ok(current);
547 }
548 current = *next;
549 }
550 if self.replacements.contains_key(¤t) {
551 return Err(GitError::InvalidObject(format!(
552 "replace depth too high for object {oid}"
553 )));
554 }
555 Ok(current)
556 }
557}
558
559type MultiPackIndexOidLookupCache = Arc<RwLock<HashMap<PathBuf, Arc<MultiPackIndexOidLookup>>>>;
563
564#[derive(Debug, Clone)]
569pub struct FileObjectDatabase {
570 pub(crate) loose: LooseObjectStore,
571 pub(crate) objects_dir: PathBuf,
572 pub(crate) alternates: Vec<PathBuf>,
573 pub(crate) format: ObjectFormat,
574 pub(crate) pack_bytes: PackBytesCache,
575 pub(crate) pack_indexes: PackIndexCache,
576 pub(crate) pack_reverse_indexes: PackReverseIndexCache,
577 pub(crate) multi_pack_indexes: MultiPackIndexCache,
578 pub(crate) multi_pack_oid_lookups: MultiPackIndexOidLookupCache,
579 pub(crate) pack_registry: PackRegistryCache,
580 pub(crate) decoded: DecodedObjectCache,
581 pub(crate) pack_deltas: PackDeltaCaches,
582 pub(crate) pack_header_types: PackHeaderTypeCaches,
583 pub(crate) promisor_objects: Arc<OnceLock<HashSet<ObjectId>>>,
584 pub(crate) promisor_remote_present: bool,
592 pub(crate) shallow_grafts: Arc<std::sync::OnceLock<HashSet<ObjectId>>>,
596 pub(crate) replacements: Arc<ObjectReplacements>,
597}
598
599fn read_shallow_grafts(shallow_file: &Path, format: ObjectFormat) -> HashSet<ObjectId> {
600 let Ok(contents) = std::fs::read_to_string(shallow_file) else {
601 return HashSet::new();
602 };
603 contents
604 .lines()
605 .filter_map(|line| ObjectId::from_hex(format, line.trim()).ok())
606 .collect()
607}
608
609impl FileObjectDatabase {
610 pub fn object_format(&self) -> ObjectFormat {
612 self.format
613 }
614
615 pub fn objects_dir(&self) -> &Path {
617 &self.objects_dir
618 }
619
620 pub fn new(objects_dir: impl Into<PathBuf>, format: ObjectFormat) -> Self {
621 let objects_dir = objects_dir.into();
622 Self {
623 loose: LooseObjectStore::new(objects_dir.clone(), format),
624 alternates: alternate_object_dirs(&objects_dir),
625 objects_dir,
626 format,
627 pack_bytes: Arc::new(RwLock::new(HashMap::new())),
628 pack_indexes: Arc::new(RwLock::new(HashMap::new())),
629 pack_reverse_indexes: Arc::new(RwLock::new(HashMap::new())),
630 multi_pack_indexes: Arc::new(RwLock::new(HashMap::new())),
631 multi_pack_oid_lookups: Arc::new(RwLock::new(HashMap::new())),
632 pack_registry: Arc::new(Mutex::new(None)),
633 decoded: Arc::new(Mutex::new(LruObjectCache::new(object_cache_budget()))),
634 pack_deltas: Arc::new(Mutex::new(HashMap::new())),
635 pack_header_types: Arc::new(Mutex::new(HashMap::new())),
636 promisor_objects: Arc::new(OnceLock::new()),
637 promisor_remote_present: false,
638 shallow_grafts: Arc::new(std::sync::OnceLock::new()),
639 replacements: Arc::new(ObjectReplacements::default()),
640 }
641 }
642
643 pub(crate) fn without_alternates(
644 objects_dir: impl Into<PathBuf>,
645 format: ObjectFormat,
646 ) -> Self {
647 let objects_dir = objects_dir.into();
648 Self {
649 loose: LooseObjectStore::new(objects_dir.clone(), format),
650 alternates: Vec::new(),
651 objects_dir,
652 format,
653 pack_bytes: Arc::new(RwLock::new(HashMap::new())),
654 pack_indexes: Arc::new(RwLock::new(HashMap::new())),
655 pack_reverse_indexes: Arc::new(RwLock::new(HashMap::new())),
656 multi_pack_indexes: Arc::new(RwLock::new(HashMap::new())),
657 multi_pack_oid_lookups: Arc::new(RwLock::new(HashMap::new())),
658 pack_registry: Arc::new(Mutex::new(None)),
659 decoded: Arc::new(Mutex::new(LruObjectCache::new(object_cache_budget()))),
660 pack_deltas: Arc::new(Mutex::new(HashMap::new())),
661 pack_header_types: Arc::new(Mutex::new(HashMap::new())),
662 promisor_objects: Arc::new(OnceLock::new()),
663 promisor_remote_present: false,
664 shallow_grafts: Arc::new(std::sync::OnceLock::new()),
665 replacements: Arc::new(ObjectReplacements::default()),
666 }
667 }
668
669 pub fn from_git_dir(git_dir: impl AsRef<Path>, format: ObjectFormat) -> Self {
670 let git_dir = git_dir.as_ref();
671 let shared_repository = sley_formats::SharedRepositoryPermissions::from_git_dir(git_dir);
672 let mut database = Self::new(repository_objects_dir(git_dir), format);
673 database.loose = database.loose.with_shared_repository(shared_repository);
674 database
675 }
676
677 pub fn with_replacements(mut self, replacements: ObjectReplacements) -> Self {
680 self.replacements = Arc::new(replacements);
681 self
682 }
683
684 pub fn replacements(&self) -> &ObjectReplacements {
685 &self.replacements
686 }
687
688 pub fn replacement_oid(&self, oid: &ObjectId) -> Result<ObjectId> {
689 self.replacements.resolve(oid)
690 }
691
692 pub fn read_object_without_replacement(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
695 self.read_object_raw(oid)
696 }
697
698 pub fn read_object_header_without_replacement(
700 &self,
701 oid: &ObjectId,
702 ) -> Result<Option<(ObjectType, u64)>> {
703 self.read_object_header_raw(oid)
704 }
705
706 pub fn with_promisor_remote_present(mut self, present: bool) -> Self {
715 self.promisor_remote_present = present;
716 self
717 }
718
719 pub fn refresh_read_cache(&self) {
724 if let Ok(mut cache) = self.pack_registry.lock() {
725 *cache = None;
726 }
727 self.pack_indexes.write().clear();
728 self.multi_pack_indexes.write().clear();
729 self.multi_pack_oid_lookups.write().clear();
730 self.pack_bytes.write().clear();
731 if let Ok(mut cache) = self.pack_deltas.lock() {
732 cache.clear();
733 }
734 if let Ok(mut cache) = self.pack_header_types.lock() {
735 cache.clear();
736 }
737 if let Ok(mut cache) = self.decoded.lock() {
738 cache.clear();
739 }
740 self.loose.invalidate_cache();
741 }
742
743 pub fn loose(&self) -> &LooseObjectStore {
744 &self.loose
745 }
746
747 pub fn presence_checker(&self) -> ObjectPresenceChecker {
748 ObjectPresenceChecker::new(self.clone())
749 }
750
751 pub fn contains(&self, oid: &ObjectId) -> Result<bool> {
752 if self.loose.exists(oid)? {
753 return Ok(true);
754 }
755 if self.find_pack_containing(oid)?.is_some() {
756 return Ok(true);
757 }
758 for alternate in &self.alternates {
759 if Self::without_alternates(alternate, self.format).contains(oid)? {
760 return Ok(true);
761 }
762 }
763 self.loose.invalidate_cache();
766 self.loose.exists(oid)
767 }
768
769 pub fn object_ids(&self) -> Result<Vec<ObjectId>> {
770 let mut oids = object_ids_in_objects_dir(&self.objects_dir, self.format)?
771 .into_iter()
772 .collect::<HashSet<_>>();
773 for alternate in &self.alternates {
774 oids.extend(Self::without_alternates(alternate, self.format).object_ids()?);
775 }
776 let mut oids = oids.into_iter().collect::<Vec<_>>();
777 oids.sort_by_key(ObjectId::to_hex);
778 Ok(oids)
779 }
780
781 pub fn object_storage_info(&self, oid: &ObjectId) -> Result<Option<ObjectStorageInfo>> {
782 if let Some(disk_size) = self.loose.disk_size(oid)? {
783 return Ok(Some(ObjectStorageInfo {
784 disk_size,
785 deltabase: zero_oid(self.format)?,
786 }));
787 }
788 if let Some(info) = self.packed_object_storage_info(oid)? {
789 return Ok(Some(info));
790 }
791 for alternate in &self.alternates {
792 if let Some(info) =
793 Self::without_alternates(alternate, self.format).object_storage_info(oid)?
794 {
795 return Ok(Some(info));
796 }
797 }
798 self.loose.invalidate_cache();
801 if let Some(disk_size) = self.loose.disk_size(oid)? {
802 return Ok(Some(ObjectStorageInfo {
803 disk_size,
804 deltabase: zero_oid(self.format)?,
805 }));
806 }
807 Ok(None)
808 }
809
810 pub fn resolve_prefix(&self, prefix: &str) -> Result<ObjectPrefixResolution> {
811 let mut matches = self.object_ids_with_prefix(prefix)?;
812 Ok(match matches.len() {
813 0 => ObjectPrefixResolution::Missing,
814 1 => ObjectPrefixResolution::Unique(matches.remove(0)),
815 _ => ObjectPrefixResolution::Ambiguous(matches),
816 })
817 }
818
819 pub fn object_ids_with_prefix(&self, prefix: &str) -> Result<Vec<ObjectId>> {
820 validate_object_id_prefix(self.format, prefix)?;
821 let prefix_bytes = prefix.as_bytes();
822 let mut matches =
823 object_ids_with_prefix_in_objects_dir(&self.objects_dir, self.format, prefix_bytes)?
824 .into_iter()
825 .collect::<HashSet<_>>();
826 for alternate in &self.alternates {
827 for oid in
828 Self::without_alternates(alternate, self.format).object_ids_with_prefix(prefix)?
829 {
830 matches.insert(oid);
831 }
832 }
833 let mut matches = matches.into_iter().collect::<Vec<_>>();
834 matches.sort_by_key(ObjectId::to_hex);
835 Ok(matches)
836 }
837
838 pub fn read_object_header(&self, oid: &ObjectId) -> Result<Option<(ObjectType, u64)>> {
848 let read_oid = self.replacements.resolve(oid)?;
849 self.read_object_header_raw(&read_oid)
850 }
851
852 fn read_object_header_raw(&self, oid: &ObjectId) -> Result<Option<(ObjectType, u64)>> {
853 if implied_empty_tree_object(self.format, oid).is_some() {
854 return Ok(Some((ObjectType::Tree, 0)));
855 }
856 if let Ok(mut cache) = self.decoded.lock()
857 && let Some(object) = cache.get(oid)
858 {
859 return Ok(Some((object.object_type, object.body.len() as u64)));
860 }
861 if let Some(header) = self.loose.read_header(oid)? {
862 return Ok(Some(header));
863 }
864 if let Some(pack_lookup) = self.find_pack_containing(oid)? {
865 let bytes = pack_lookup.pack_bytes(self)?;
866 let type_cache = pack_lookup.header_type_cache(self);
871 let resolve_ref_base = |base: &ObjectId| {
872 self.read_object_header_raw(base)
873 .map(|header| header.map(|(t, _)| t))
874 };
875 let header = match &type_cache {
876 Some(cache) => {
877 let mut adapter = PackHeaderTypeCacheAdapter(cache);
878 sley_pack::read_object_header_at_with_cache(
879 &bytes,
880 pack_lookup.offset,
881 self.format,
882 resolve_ref_base,
883 &mut adapter,
884 )?
885 }
886 None => sley_pack::read_object_header_at(
887 &bytes,
888 pack_lookup.offset,
889 self.format,
890 resolve_ref_base,
891 )?,
892 };
893 return Ok(Some(header));
894 }
895 for alternate in &self.alternates {
896 if let Some(header) =
897 Self::without_alternates(alternate, self.format).read_object_header_raw(oid)?
898 {
899 return Ok(Some(header));
900 }
901 }
902 self.loose.invalidate_cache();
905 if let Some(header) = self.loose.read_header(oid)? {
906 return Ok(Some(header));
907 }
908 Ok(None)
909 }
910
911 pub(crate) fn read_packed_object(&self, oid: &ObjectId) -> Result<Option<Arc<EncodedObject>>> {
912 if let Ok(mut cache) = self.decoded.lock()
915 && let Some(object) = cache.get(oid)
916 {
917 return Ok(Some(object));
918 }
919 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
920 return Ok(None);
921 };
922 self.read_packed_object_at_lookup(oid, &pack_lookup)
923 .map(Some)
924 }
925
926 pub(crate) fn read_packed_object_at_lookup(
927 &self,
928 oid: &ObjectId,
929 pack_lookup: &PackLookup,
930 ) -> Result<Arc<EncodedObject>> {
931 if let Ok(mut cache) = self.decoded.lock()
932 && let Some(object) = cache.get(oid)
933 {
934 return Ok(object);
935 }
936 let bytes = pack_lookup.pack_bytes(self)?;
937 let delta_cache = pack_lookup.delta_cache(self);
942 let delta_adapter = delta_cache.as_ref().map(PackDeltaCacheAdapter);
943 let resolve_ref_base = |base: &ObjectId| self.read_object_raw(base).map(Some);
949 let resolve_ofs_base =
950 |base_offset| self.read_ofs_delta_base_from_other_sources(pack_lookup, base_offset);
951 let object = match &delta_adapter {
952 Some(adapter) => sley_pack::read_object_at_with_cache_and_ofs_base_arc(
953 &bytes,
954 pack_lookup.offset,
955 self.format,
956 resolve_ref_base,
957 resolve_ofs_base,
958 adapter,
959 )?,
960 None => sley_pack::read_object_at_with_ofs_base_arc(
961 &bytes,
962 pack_lookup.offset,
963 self.format,
964 resolve_ref_base,
965 resolve_ofs_base,
966 )?,
967 };
968 if verify_reads_enabled() {
972 let actual = object.object_id(self.format)?;
973 if actual != *oid {
974 return Err(GitError::InvalidObject(format!(
975 "pack object id mismatch: index says {oid}, decoded {actual}"
976 )));
977 }
978 }
979 if let Ok(mut cache) = self.decoded.lock() {
980 cache.put(*oid, Arc::clone(&object));
981 }
982 Ok(object)
983 }
984
985 pub(crate) fn pack_delta_cache(&self, pack_path: &Path) -> Option<Arc<Mutex<LruOffsetCache>>> {
989 let mut caches = self.pack_deltas.lock().ok()?;
990 let cache = caches.entry(pack_path.to_path_buf()).or_insert_with(|| {
991 Arc::new(Mutex::new(LruOffsetCache::new(delta_base_cache_budget())))
992 });
993 Some(Arc::clone(cache))
994 }
995
996 pub(crate) fn pack_header_type_cache(&self, pack_path: &Path) -> Option<PackHeaderTypeCache> {
1000 let mut caches = self.pack_header_types.lock().ok()?;
1001 let cache = caches
1002 .entry(pack_path.to_path_buf())
1003 .or_insert_with(|| Arc::new(Mutex::new(HashMap::new())));
1004 Some(Arc::clone(cache))
1005 }
1006
1007 pub(crate) fn cached_pack_bytes(&self, pack_path: &Path) -> Result<Arc<PackData>> {
1012 if let Some(bytes) = self.pack_bytes.read().get(pack_path) {
1013 return Ok(Arc::clone(bytes));
1014 }
1015 let bytes = Arc::new(load_pack_data(pack_path)?);
1016 self.pack_bytes
1017 .write()
1018 .insert(pack_path.to_path_buf(), Arc::clone(&bytes));
1019 Ok(bytes)
1020 }
1021
1022 pub(crate) fn cached_pack_index(&self, index_path: &Path) -> Result<Arc<PackIndexViewData>> {
1026 if let Some(index) = self.pack_indexes.read().get(index_path) {
1027 return Ok(Arc::clone(index));
1028 }
1029 let index_bytes = load_pack_index_data(index_path)?;
1030 let index = Arc::new(PackIndexViewData::parse_trusted_source_without_checksum(
1031 index_bytes,
1032 self.format,
1033 )?);
1034 self.pack_indexes
1035 .write()
1036 .insert(index_path.to_path_buf(), Arc::clone(&index));
1037 Ok(index)
1038 }
1039
1040 pub(crate) fn cached_pack_reverse_index(
1043 &self,
1044 index_path: &Path,
1045 index: &PackIndexViewData,
1046 ) -> Result<Option<Arc<PackReverseIndex>>> {
1047 if let Some(cached) = self.pack_reverse_indexes.read().get(index_path) {
1048 return Ok(cached.as_ref().map(Arc::clone));
1049 }
1050 let rev_path = index_path.with_extension("rev");
1051 let reverse = if rev_path.exists() {
1052 let bytes = fs::read(&rev_path)?;
1053 match PackReverseIndex::parse(&bytes, self.format, index.count) {
1054 Ok(parsed) if parsed.pack_checksum == index.pack_checksum => Some(Arc::new(parsed)),
1055 _ => None,
1056 }
1057 } else {
1058 None
1059 };
1060 self.pack_reverse_indexes
1061 .write()
1062 .insert(index_path.to_path_buf(), reverse.as_ref().map(Arc::clone));
1063 Ok(reverse)
1064 }
1065
1066 pub(crate) fn cached_multi_pack_index_oid_lookup(
1067 &self,
1068 midx_path: &Path,
1069 ) -> Result<Option<Arc<MultiPackIndexOidLookup>>> {
1070 if !midx_path.exists() {
1071 return Ok(None);
1072 }
1073 if let Some(midx) = self.multi_pack_oid_lookups.read().get(midx_path) {
1074 return Ok(Some(Arc::clone(midx)));
1075 }
1076 let bytes = load_multi_pack_index_lookup_data(midx_path)?;
1077 let midx = match MultiPackIndexOidLookup::parse(bytes, self.format) {
1078 Ok(midx) => Arc::new(midx),
1079 Err(GitError::InvalidFormat(message))
1080 if message.starts_with("multi-pack-index hash id ") =>
1081 {
1082 let actual = message
1083 .strip_prefix("multi-pack-index hash id ")
1084 .and_then(|rest| rest.split_whitespace().next())
1085 .unwrap_or("0");
1086 let expected = match self.format {
1087 ObjectFormat::Sha1 => 1,
1088 ObjectFormat::Sha256 => 2,
1089 };
1090 eprintln!(
1091 "error: multi-pack-index hash version {actual} does not match version {expected}"
1092 );
1093 return Ok(None);
1094 }
1095 Err(err) => return Err(err),
1096 };
1097 self.multi_pack_oid_lookups
1098 .write()
1099 .insert(midx_path.to_path_buf(), Arc::clone(&midx));
1100 Ok(Some(midx))
1101 }
1102
1103 pub(crate) fn cached_multi_pack_index(
1104 &self,
1105 midx_path: &Path,
1106 ) -> Result<Option<Arc<MultiPackIndex>>> {
1107 if !midx_path.exists() {
1108 return Ok(None);
1109 }
1110 if let Some(midx) = self.multi_pack_indexes.read().get(midx_path) {
1111 return Ok(Some(Arc::clone(midx)));
1112 }
1113 let bytes = load_multi_pack_index_lookup_data(midx_path)?;
1114 let midx = match MultiPackIndex::parse(bytes.as_bytes(), self.format) {
1115 Ok(midx) => Arc::new(midx),
1116 Err(GitError::InvalidFormat(message))
1117 if message.starts_with("multi-pack-index hash id ") =>
1118 {
1119 let actual = message
1120 .strip_prefix("multi-pack-index hash id ")
1121 .and_then(|rest| rest.split_whitespace().next())
1122 .unwrap_or("0");
1123 let expected = match self.format {
1124 ObjectFormat::Sha1 => 1,
1125 ObjectFormat::Sha256 => 2,
1126 };
1127 eprintln!(
1128 "error: multi-pack-index hash version {actual} does not match version {expected}"
1129 );
1130 return Ok(None);
1131 }
1132 Err(err) => return Err(err),
1133 };
1134 self.multi_pack_indexes
1135 .write()
1136 .insert(midx_path.to_path_buf(), Arc::clone(&midx));
1137 Ok(Some(midx))
1138 }
1139
1140 pub(crate) fn cached_pack_registry(
1145 &self,
1146 pack_dir: &Path,
1147 force_rescan: bool,
1148 ) -> Result<Arc<PackRegistrySnapshot>> {
1149 if !force_rescan && let Some(registry) = self.cached_loaded_pack_registry(pack_dir)? {
1150 return Ok(registry);
1151 }
1152 let scanned = Arc::new(scan_pack_registry(pack_dir, self.format)?);
1153 if let Ok(mut cache) = self.pack_registry.lock() {
1154 match cache.as_ref() {
1155 Some(existing)
1156 if existing.fingerprint == scanned.fingerprint
1157 && same_registered_pack_set(&existing.packs, &scanned.packs) =>
1158 {
1159 return Ok(Arc::clone(existing));
1160 }
1161 _ => {
1162 *cache = Some(Arc::clone(&scanned));
1163 }
1164 }
1165 }
1166 Ok(scanned)
1167 }
1168
1169 pub(crate) fn find_in_pack_registry(
1170 &self,
1171 registry: Arc<PackRegistrySnapshot>,
1172 oid: &ObjectId,
1173 ) -> Result<Option<PackLookup>> {
1174 let hinted_pack_index = registry.cached_hint();
1175 if let Some(pack_index) = hinted_pack_index {
1176 let pack = ®istry.packs[pack_index];
1177 match pack.index(self.format) {
1178 Ok(index) => {
1179 if let Some(entry) = index.find(oid) {
1180 return Ok(Some(PackLookup::from_registered(
1181 Arc::clone(pack),
1182 entry.offset,
1183 )));
1184 }
1185 }
1186 Err(_) => {
1187 eprintln!("error: packfile {} index unavailable", pack.pack.display());
1188 }
1189 }
1190 }
1191 for (pack_index, pack) in registry.packs.iter().enumerate() {
1192 if Some(pack_index) == hinted_pack_index {
1193 continue;
1194 }
1195 let index = match pack.index(self.format) {
1196 Ok(index) => index,
1197 Err(_) => {
1198 eprintln!("error: packfile {} index unavailable", pack.pack.display());
1199 continue;
1200 }
1201 };
1202 if let Some(entry) = index.find(oid) {
1203 registry.remember_hint(pack_index);
1204 return Ok(Some(PackLookup::from_registered(
1205 Arc::clone(pack),
1206 entry.offset,
1207 )));
1208 }
1209 }
1210 Ok(None)
1211 }
1212
1213 pub(crate) fn read_packed_object_from_other_packs(
1219 &self,
1220 oid: &ObjectId,
1221 exclude: &PackLookup,
1222 ) -> Result<Option<Arc<EncodedObject>>> {
1223 let pack_dir = self.objects_dir.join("pack");
1224 let Ok(entries) = fs::read_dir(&pack_dir) else {
1225 return Ok(None);
1226 };
1227 let excluded_pack = exclude.pack_path().to_path_buf();
1228 for entry in entries {
1229 let idx_path = entry?.path();
1230 if idx_path.extension().and_then(|ext| ext.to_str()) != Some("idx") {
1231 continue;
1232 }
1233 let pack_path = idx_path.with_extension("pack");
1234 if pack_path == excluded_pack {
1235 continue;
1236 }
1237 let Ok(idx_bytes) = fs::read(&idx_path) else {
1238 continue;
1239 };
1240 let Ok(index) = PackIndex::parse(&idx_bytes, self.format) else {
1241 continue;
1242 };
1243 let Some(entry) = index.find(oid) else {
1244 continue;
1245 };
1246 let candidate = PackLookup::from_path(pack_path, entry.offset);
1247 if let Ok(object) = self.read_packed_object_at_lookup(oid, &candidate) {
1248 return Ok(Some(object));
1249 }
1250 }
1251 Ok(None)
1252 }
1253
1254 pub(crate) fn pack_oid_at_offset(
1255 &self,
1256 pack_lookup: &PackLookup,
1257 offset: u64,
1258 ) -> Result<Option<ObjectId>> {
1259 let index_path = match &pack_lookup.registered {
1260 Some(pack) => pack.idx.clone(),
1261 None => pack_lookup.pack.with_extension("idx"),
1262 };
1263 match pack_lookup.pack_index(self) {
1264 Ok(index) => {
1265 if let Some(reverse) = self.cached_pack_reverse_index(&index_path, &index)? {
1266 Ok(reverse.oid_at_offset(&index, offset))
1267 } else {
1268 Ok(index.oid_at_offset_linear(offset))
1269 }
1270 }
1271 Err(_) => self.midx_oid_for_pack_offset(pack_lookup, offset),
1272 }
1273 }
1274
1275 pub(crate) fn read_ofs_delta_base_from_other_sources(
1276 &self,
1277 pack_lookup: &PackLookup,
1278 base_offset: u64,
1279 ) -> Result<Option<Arc<EncodedObject>>> {
1280 let Some(base_oid) = self.pack_oid_at_offset(pack_lookup, base_offset)? else {
1281 return Ok(None);
1282 };
1283 if let Ok(mut cache) = self.decoded.lock()
1284 && let Some(object) = cache.get(&base_oid)
1285 {
1286 return Ok(Some(object));
1287 }
1288 if let Ok(object) = self.loose.read_object(&base_oid) {
1289 return Ok(Some(object));
1290 }
1291 if let Some(object) = self.read_packed_object_from_other_packs(&base_oid, pack_lookup)? {
1292 return Ok(Some(object));
1293 }
1294 for alternate in &self.alternates {
1295 if let Ok(object) =
1296 Self::without_alternates(alternate, self.format).read_object(&base_oid)
1297 {
1298 return Ok(Some(object));
1299 }
1300 }
1301 Ok(None)
1302 }
1303
1304 pub(crate) fn find_pack_containing(&self, oid: &ObjectId) -> Result<Option<PackLookup>> {
1305 if oid.format() != self.format {
1306 return Err(GitError::InvalidObjectId(format!(
1307 "object {oid} uses {}, store uses {}",
1308 oid.format().name(),
1309 self.format.name()
1310 )));
1311 }
1312 let pack_dir = self.objects_dir.join("pack");
1313 if let Some(midx) = self.cached_loaded_multi_pack_index_oid_lookup()
1318 && let Some(pack_paths) = self.midx_oid_lookup_pack_paths(&pack_dir, &midx, oid)?
1319 {
1320 return Ok(Some(pack_paths));
1321 }
1322 if let Some(registry) = self.cached_loaded_pack_registry(&pack_dir)?
1323 && let Some(pack_paths) = self.find_in_pack_registry(registry, oid)?
1324 {
1325 return Ok(Some(pack_paths));
1326 }
1327
1328 if !pack_dir.exists() {
1329 return Ok(None);
1330 }
1331 if let Some(pack_paths) = self.find_midx_pack_containing(&pack_dir, oid)? {
1332 return Ok(Some(pack_paths));
1333 }
1334 let registry = self.cached_pack_registry(&pack_dir, false)?;
1338 if let Some(pack_paths) = self.find_in_pack_registry(Arc::clone(®istry), oid)? {
1339 return Ok(Some(pack_paths));
1340 }
1341 let refreshed = self.cached_pack_registry(&pack_dir, true)?;
1342 if Arc::ptr_eq(®istry, &refreshed) {
1343 return Ok(None);
1345 }
1346 self.find_in_pack_registry(refreshed, oid)
1347 }
1348
1349 pub(crate) fn packed_object_storage_info(
1350 &self,
1351 oid: &ObjectId,
1352 ) -> Result<Option<ObjectStorageInfo>> {
1353 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1354 return Ok(None);
1355 };
1356 let index = pack_lookup.pack_index(self).ok();
1357 let pack = match pack_lookup.pack_bytes(self) {
1358 Ok(pack) => Some(pack),
1359 Err(_err) if index.is_some() => None,
1360 Err(err) => return Err(err),
1361 };
1362 let trailer_offset = pack
1363 .as_ref()
1364 .map(|pack| {
1365 (pack.len() as u64)
1366 .checked_sub(self.format.raw_len() as u64)
1367 .ok_or_else(|| {
1368 GitError::InvalidFormat("pack file shorter than checksum".into())
1369 })
1370 })
1371 .transpose()?;
1372 let delta_base = match &pack {
1373 Some(pack) => pack_entry_delta_base(self.format, pack, pack_lookup.offset)?,
1374 None => None,
1375 };
1376 let delta_base_offset = match &delta_base {
1377 Some(PackDeltaBase::Offset(offset)) => Some(*offset),
1378 Some(PackDeltaBase::Ref(_)) | None => None,
1379 };
1380 let offset_info = if let Some(index) = &index {
1381 scan_pack_index_offsets(index, pack_lookup.offset, trailer_offset, delta_base_offset)?
1382 } else if let Some(pack) = &pack {
1383 let end_offset =
1384 scan_pack_offsets_without_index(self.format, pack, pack_lookup.offset)?
1385 .ok_or_else(|| {
1386 GitError::InvalidFormat(format!(
1387 "pack offset {} not found",
1388 pack_lookup.offset
1389 ))
1390 })?;
1391 let delta_base_oid = match delta_base_offset {
1392 Some(offset) => self
1393 .midx_oid_for_pack_offset(&pack_lookup, offset)?
1394 .ok_or_else(|| {
1395 GitError::InvalidFormat(format!("ofs-delta base offset {offset} not found"))
1396 })?,
1397 None => zero_oid(self.format)?,
1398 };
1399 PackIndexOffsetInfo {
1400 end_offset,
1401 delta_base_oid: delta_base_offset.map(|_| delta_base_oid),
1402 }
1403 } else {
1404 return Err(GitError::InvalidFormat(
1405 "packed object metadata source unavailable".into(),
1406 ));
1407 };
1408 let disk_size = offset_info
1409 .end_offset
1410 .checked_sub(pack_lookup.offset)
1411 .ok_or_else(|| GitError::InvalidFormat("pack index offsets are not sorted".into()))?;
1412 let deltabase = match delta_base {
1413 Some(PackDeltaBase::Offset(_)) => offset_info.delta_base_oid.ok_or_else(|| {
1414 GitError::InvalidFormat("ofs-delta base oid missing from pack index".into())
1420 })?,
1421 Some(PackDeltaBase::Ref(oid)) => oid,
1422 None => zero_oid(self.format)?,
1423 };
1424 Ok(Some(ObjectStorageInfo {
1425 disk_size,
1426 deltabase,
1427 }))
1428 }
1429
1430 pub(crate) fn midx_oid_for_pack_offset(
1431 &self,
1432 pack_lookup: &PackLookup,
1433 offset: u64,
1434 ) -> Result<Option<ObjectId>> {
1435 let pack_dir = self.objects_dir.join("pack");
1436 let midx_path = pack_dir.join("multi-pack-index");
1437 let Some(midx) = self.cached_multi_pack_index(&midx_path)? else {
1438 return Ok(None);
1439 };
1440 let Some(pack_name) = pack_lookup
1441 .pack_path()
1442 .file_name()
1443 .and_then(|name| name.to_str())
1444 else {
1445 return Ok(None);
1446 };
1447 let idx_name = pack_name
1448 .strip_suffix(".pack")
1449 .map(|stem| format!("{stem}.idx"))
1450 .unwrap_or_else(|| pack_name.to_string());
1451 let Some(pack_int_id) = midx
1452 .pack_names
1453 .iter()
1454 .position(|candidate| candidate == &idx_name)
1455 else {
1456 return Ok(None);
1457 };
1458 Ok(midx
1459 .objects
1460 .iter()
1461 .find(|entry| entry.pack_int_id == pack_int_id as u32 && entry.offset == offset)
1462 .map(|entry| entry.oid))
1463 }
1464
1465 pub(crate) fn find_midx_pack_containing(
1466 &self,
1467 pack_dir: &Path,
1468 oid: &ObjectId,
1469 ) -> Result<Option<PackLookup>> {
1470 let midx_path = pack_dir.join("multi-pack-index");
1471 if let Some(midx) = self.cached_multi_pack_index_oid_lookup(&midx_path)?
1472 && let Some(pack_lookup) = self.midx_oid_lookup_pack_paths(pack_dir, &midx, oid)?
1473 {
1474 return Ok(Some(pack_lookup));
1475 }
1476 self.find_incremental_midx_pack_containing(pack_dir, oid)
1477 }
1478
1479 pub(crate) fn midx_oid_lookup_pack_paths(
1480 &self,
1481 pack_dir: &Path,
1482 midx: &MultiPackIndexOidLookup,
1483 oid: &ObjectId,
1484 ) -> Result<Option<PackLookup>> {
1485 let Some(entry) = midx.find(oid)? else {
1486 return Ok(None);
1487 };
1488 let Some(pack_name) = midx.pack_name(entry.pack_int_id) else {
1489 return Err(GitError::InvalidFormat(
1490 "multi-pack-index object points past pack table".into(),
1491 ));
1492 };
1493 let pack_file_name = pack_name
1494 .strip_suffix(".idx")
1495 .map(|stem| format!("{stem}.pack"))
1496 .unwrap_or_else(|| pack_name.to_string());
1497 let pack = pack_dir.join(pack_file_name);
1498 Ok(Some(PackLookup::from_path(pack, entry.offset)))
1499 }
1500
1501 pub(crate) fn find_incremental_midx_pack_containing(
1502 &self,
1503 pack_dir: &Path,
1504 oid: &ObjectId,
1505 ) -> Result<Option<PackLookup>> {
1506 let chain = read_incremental_midx_chain(pack_dir)?;
1507 if chain.is_empty() {
1508 return Ok(None);
1509 }
1510 let midx_dir = pack_dir.join("multi-pack-index.d");
1511 for checksum in chain.iter().rev() {
1512 let path = midx_dir.join(format!("multi-pack-index-{checksum}.midx"));
1513 if !path.exists() {
1514 continue;
1515 }
1516 let bytes = load_multi_pack_index_lookup_data(&path)?;
1517 let midx = match MultiPackIndexOidLookup::parse(bytes, self.format) {
1518 Ok(midx) => midx,
1519 Err(_) => continue,
1520 };
1521 if let Some(pack_lookup) = self.midx_oid_lookup_pack_paths(pack_dir, &midx, oid)? {
1522 return Ok(Some(pack_lookup));
1523 }
1524 }
1525 Ok(None)
1526 }
1527
1528 pub(crate) fn cached_loaded_multi_pack_index_oid_lookup(
1529 &self,
1530 ) -> Option<Arc<MultiPackIndexOidLookup>> {
1531 let midx_path = self.objects_dir.join("pack").join("multi-pack-index");
1532 self.multi_pack_oid_lookups
1533 .read()
1534 .get(&midx_path)
1535 .map(Arc::clone)
1536 }
1537
1538 pub(crate) fn cached_loaded_pack_registry(
1544 &self,
1545 _pack_dir: &Path,
1546 ) -> Result<Option<Arc<PackRegistrySnapshot>>> {
1547 let cache = match self.pack_registry.lock() {
1548 Ok(cache) => cache,
1549 Err(_) => return Ok(None),
1550 };
1551 Ok(cache.as_ref().map(Arc::clone))
1552 }
1553}
1554impl ObjectReader for FileObjectDatabase {
1555 fn reusable_delta_base(&self, oid: &ObjectId) -> Result<Option<ObjectId>> {
1556 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1557 return Ok(None);
1558 };
1559 let pack = pack_lookup.pack_bytes(self)?;
1560 match pack_entry_delta_base(self.format, &pack, pack_lookup.offset)? {
1561 Some(PackDeltaBase::Ref(base_oid)) => Ok(Some(base_oid)),
1562 Some(PackDeltaBase::Offset(base_offset)) => {
1563 let base_oid = self
1564 .pack_oid_at_offset(&pack_lookup, base_offset)?
1565 .ok_or_else(|| {
1566 GitError::InvalidFormat(format!(
1567 "ofs-delta base offset {base_offset} not found"
1568 ))
1569 })?;
1570 Ok(Some(base_oid))
1571 }
1572 None => Ok(None),
1573 }
1574 }
1575
1576 fn is_promised_object(&self, oid: &ObjectId) -> bool {
1577 self.promisor_remote_present && self.promisor_objects().contains(oid)
1583 }
1584
1585 fn has_shallow_grafts(&self) -> bool {
1586 !self
1587 .shallow_grafts
1588 .get_or_init(|| {
1589 let shallow_file = self
1590 .objects_dir
1591 .parent()
1592 .map(|git_dir| git_dir.join("shallow"));
1593 match shallow_file {
1594 Some(path) => read_shallow_grafts(&path, self.format),
1595 None => HashSet::new(),
1596 }
1597 })
1598 .is_empty()
1599 }
1600
1601 fn is_shallow_graft(&self, oid: &ObjectId) -> bool {
1602 self.shallow_grafts
1603 .get_or_init(|| {
1604 let shallow_file = self
1605 .objects_dir
1606 .parent()
1607 .map(|git_dir| git_dir.join("shallow"));
1608 match shallow_file {
1609 Some(path) => read_shallow_grafts(&path, self.format),
1610 None => HashSet::new(),
1611 }
1612 })
1613 .contains(oid)
1614 }
1615
1616 fn read_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
1617 let read_oid = self.replacements.resolve(oid)?;
1618 self.read_object_raw(&read_oid)
1619 }
1620}
1621
1622impl FileObjectDatabase {
1623 fn read_object_raw(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
1624 if let Some(object) = implied_empty_tree_object(self.format, oid) {
1625 return Ok(object);
1626 }
1627 if let Some(pack_lookup) = self.find_pack_containing(oid)? {
1635 match self.read_packed_object_at_lookup(oid, &pack_lookup) {
1636 Ok(object) => return Ok(object),
1637 Err(GitError::NotFound(_)) => {}
1638 Err(packed_err) => {
1644 if let Ok(object) = self.loose.read_object(oid) {
1645 return Ok(object);
1646 }
1647 if let Some(object) =
1650 self.read_packed_object_from_other_packs(oid, &pack_lookup)?
1651 {
1652 return Ok(object);
1653 }
1654 for alternate in &self.alternates {
1655 if let Ok(object) =
1656 Self::without_alternates(alternate, self.format).read_object_raw(oid)
1657 {
1658 return Ok(object);
1659 }
1660 }
1661 return Err(packed_err);
1662 }
1663 }
1664 }
1665 let loose_err = match self.loose.read_object(oid) {
1666 Ok(object) => return Ok(object),
1667 Err(GitError::NotFound(_)) => None,
1668 Err(err) => Some(err),
1669 };
1670 if let Some(object) = self.read_packed_object(oid)? {
1671 return Ok(object);
1672 }
1673 for alternate in &self.alternates {
1674 match Self::without_alternates(alternate, self.format).read_object_raw(oid) {
1675 Ok(object) => return Ok(object),
1676 Err(GitError::NotFound(_)) => {}
1677 Err(err) => return Err(err),
1678 }
1679 }
1680 self.loose.invalidate_cache();
1686 match self.loose.read_object(oid) {
1687 Ok(object) => return Ok(object),
1688 Err(GitError::NotFound(_)) => {}
1689 Err(err) => return Err(err),
1690 }
1691 if let Some(err) = loose_err {
1695 return Err(err);
1696 }
1697 Err(GitError::object_not_found_in(
1698 *oid,
1699 MissingObjectContext::Read,
1700 ))
1701 }
1702}
1703impl FileObjectDatabase {
1704 fn promisor_objects(&self) -> &HashSet<ObjectId> {
1705 self.promisor_objects.get_or_init(|| {
1706 let mut promised =
1707 promisor_pack_object_ids(&self.objects_dir, self.format).unwrap_or_default();
1708 let mut pending = promised.iter().copied().collect::<Vec<_>>();
1709 while let Some(oid) = pending.pop() {
1710 let Ok(object) = self.read_object(&oid) else {
1711 continue;
1712 };
1713 for link in promisor_object_links(self.format, &object) {
1714 if promised.insert(link) {
1715 pending.push(link);
1716 }
1717 }
1718 }
1719 promised
1720 })
1721 }
1722
1723 fn freshen_existing_object(&self, oid: &ObjectId) -> Result<bool> {
1724 if self.freshen_loose_object(oid)? {
1725 return Ok(true);
1726 }
1727 if self.freshen_packed_object(oid)? {
1728 return Ok(true);
1729 }
1730 for alternate in &self.alternates {
1731 if Self::without_alternates(alternate, self.format).freshen_existing_object(oid)? {
1732 return Ok(true);
1733 }
1734 }
1735 self.loose.invalidate_cache();
1737 self.freshen_loose_object(oid)
1738 }
1739
1740 fn freshen_loose_object(&self, oid: &ObjectId) -> Result<bool> {
1741 let path = self.loose.object_path(oid)?;
1742 freshen_file_mtime(&path)
1743 }
1744
1745 fn freshen_packed_object(&self, oid: &ObjectId) -> Result<bool> {
1746 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1747 return Ok(false);
1748 };
1749 if !pack_lookup.pack_path().with_extension("mtimes").exists() {
1750 return freshen_file_mtime(pack_lookup.pack_path());
1751 }
1752
1753 let pack_dir = self.objects_dir.join("pack");
1759 let Ok(entries) = fs::read_dir(pack_dir) else {
1760 return Ok(false);
1761 };
1762 for entry in entries {
1763 let idx_path = entry?.path();
1764 if idx_path.extension().and_then(|ext| ext.to_str()) != Some("idx")
1765 || idx_path.with_extension("mtimes").exists()
1766 {
1767 continue;
1768 }
1769 let pack_path = idx_path.with_extension("pack");
1770 if !pack_path.exists() {
1771 continue;
1772 }
1773 let Ok(index_bytes) = fs::read(&idx_path) else {
1774 continue;
1775 };
1776 let Ok(index) = PackIndex::parse(&index_bytes, self.format) else {
1777 continue;
1778 };
1779 if index.find(oid).is_some() {
1780 return freshen_file_mtime(&pack_path);
1781 }
1782 }
1783 Ok(false)
1784 }
1785}
1786pub(crate) fn freshen_file_mtime(path: &Path) -> Result<bool> {
1787 let file = match fs::OpenOptions::new().read(true).open(path) {
1788 Ok(file) => file,
1789 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
1790 Err(err) => return Err(GitError::Io(err.to_string())),
1791 };
1792 file.set_modified(std::time::SystemTime::now())
1793 .map_err(|err| GitError::Io(err.to_string()))?;
1794 Ok(true)
1795}
1796
1797pub(crate) fn promisor_pack_object_ids(
1798 objects_dir: &Path,
1799 format: ObjectFormat,
1800) -> Result<HashSet<ObjectId>> {
1801 let pack_dir = objects_dir.join("pack");
1802 let mut oids = HashSet::new();
1803 if !pack_dir.exists() {
1804 return Ok(oids);
1805 }
1806 for entry in fs::read_dir(pack_dir)? {
1807 let path = entry?.path();
1808 if path.extension().and_then(|ext| ext.to_str()) != Some("idx") {
1809 continue;
1810 }
1811 if !path.with_extension("pack").exists() || !path.with_extension("promisor").exists() {
1812 continue;
1813 }
1814 let index = PackIndex::parse(&fs::read(path)?, format)?;
1815 oids.extend(index.entries.into_iter().map(|entry| entry.oid));
1816 }
1817 Ok(oids)
1818}
1819
1820pub(crate) fn promisor_object_links(format: ObjectFormat, object: &EncodedObject) -> Vec<ObjectId> {
1821 match object.object_type {
1822 ObjectType::Commit => Commit::parse_ref(format, &object.body)
1823 .map(|commit| {
1824 let mut links = Vec::with_capacity(commit.parents.len() + 1);
1825 links.push(commit.tree);
1826 links.extend(commit.parents);
1827 links
1828 })
1829 .unwrap_or_default(),
1830 ObjectType::Tree => TreeEntries::new(format, &object.body)
1831 .filter_map(|entry| entry.ok().map(|entry| entry.oid))
1832 .collect(),
1833 ObjectType::Tag => Tag::parse_ref(format, &object.body)
1834 .map(|tag| vec![tag.object])
1835 .unwrap_or_default(),
1836 ObjectType::Blob => Vec::new(),
1837 }
1838}
1839impl ObjectWriter for FileObjectDatabase {
1840 fn write_object(&self, object: EncodedObject) -> Result<ObjectId> {
1841 let oid = object.object_id(self.format)?;
1848 if self.freshen_existing_object(&oid)? {
1849 return Ok(oid);
1850 }
1851 self.loose.write_object(object)
1852 }
1853}