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 smallvec::SmallVec;
29use std::collections::{HashMap, HashSet};
30use std::path::{Path, PathBuf};
31use std::sync::Mutex;
32use std::sync::{Arc, OnceLock};
33use std::{env, fs};
34
35use crate::{ObjectReader, ObjectWriter, ReusablePackCandidate, implied_empty_tree_object};
36
37use crate::install::{ObjectPrefixResolution, ObjectStorageInfo};
38use crate::loose::LooseObjectStore;
39use crate::reachability::{
40 PackDeltaBase, PackIndexOffsetInfo, pack_entry_delta_base, scan_pack_index_offsets,
41 scan_pack_offsets_without_index,
42};
43use crate::registry::{
44 ObjectPresenceChecker, PackLookup, PackRegistryCache, PackRegistrySnapshot,
45 alternate_object_dirs, object_ids_in_objects_dir, object_ids_with_prefix_in_objects_dir,
46 read_incremental_midx_chain, repository_objects_dir, same_registered_pack_set,
47 scan_pack_registry, validate_object_id_prefix,
48};
49
50pub struct ObjectDatabase {
51 pub(crate) format: ObjectFormat,
52 pub(crate) objects: Mutex<HashMap<ObjectId, Arc<EncodedObject>>>,
58 pub(crate) promisor: bool,
59}
60
61impl ObjectDatabase {
62 pub fn new(format: ObjectFormat) -> Self {
63 Self {
64 format,
65 objects: Mutex::new(HashMap::new()),
66 promisor: false,
67 }
68 }
69
70 pub fn with_promisor(mut self, promisor: bool) -> Self {
71 self.promisor = promisor;
72 self
73 }
74
75 pub fn contains(&self, oid: &ObjectId) -> bool {
76 self.objects
77 .lock()
78 .map(|objects| objects.contains_key(oid))
79 .unwrap_or(false)
80 }
81
82 pub fn validate(&self, oid: &ObjectId) -> Result<()> {
83 let object = self.read_object(oid)?;
84 let actual = object.object_id(self.format)?;
85 if &actual == oid {
86 Ok(())
87 } else {
88 Err(GitError::InvalidObject(format!(
89 "object id mismatch: expected {oid}, got {actual}"
90 )))
91 }
92 }
93}
94
95impl ObjectReader for ObjectDatabase {
96 fn read_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
97 self.objects
98 .lock()
99 .map_err(|_| GitError::object_not_found_in(*oid, MissingObjectContext::Read))?
100 .get(oid)
101 .map(Arc::clone)
102 .or_else(|| implied_empty_tree_object(self.format, oid))
103 .ok_or_else(|| GitError::object_not_found_in(*oid, MissingObjectContext::Read))
104 }
105}
106
107impl ObjectWriter for ObjectDatabase {
108 fn write_object(&self, object: EncodedObject) -> Result<ObjectId> {
109 let oid = object.object_id(self.format)?;
110 self.objects
111 .lock()
112 .map_err(|_| GitError::Io("object cache lock poisoned".into()))?
113 .entry(oid)
114 .or_insert_with(|| Arc::new(object));
115 Ok(oid)
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Alternate {
121 pub path: std::path::PathBuf,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct PartialClonePolicy {
126 pub promisor_remote: Option<String>,
127 pub allow_missing_promised_objects: bool,
128}
129
130pub(crate) type PackBytesCache = Arc<RwLock<HashMap<PathBuf, Arc<PackData>>>>;
134
135#[derive(Debug)]
138pub(crate) enum PackData {
139 #[cfg(feature = "mmap")]
140 Mapped(sley_mmap::MappedFile),
141 Heap(Vec<u8>),
142}
143
144impl std::ops::Deref for PackData {
145 type Target = [u8];
146
147 fn deref(&self) -> &[u8] {
148 match self {
149 #[cfg(feature = "mmap")]
150 Self::Mapped(mapped) => mapped,
151 Self::Heap(bytes) => bytes,
152 }
153 }
154}
155
156#[cfg(feature = "mmap")]
159pub(crate) fn load_pack_data(pack_path: &Path) -> Result<PackData> {
160 match sley_mmap::MappedFile::open_pack(pack_path) {
161 Ok(mapped) => Ok(PackData::Mapped(mapped)),
162 Err(_) => Ok(PackData::Heap(fs::read(pack_path)?)),
163 }
164}
165
166#[cfg(not(feature = "mmap"))]
167pub(crate) fn load_pack_data(pack_path: &Path) -> Result<PackData> {
168 Ok(PackData::Heap(fs::read(pack_path)?))
169}
170
171#[cfg(feature = "mmap")]
172pub(crate) fn load_pack_index_data(index_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
173 match sley_mmap::MappedFile::open_pack(index_path) {
174 Ok(mapped) => Ok(Arc::new(mapped)),
175 Err(_) => Ok(Arc::new(fs::read(index_path)?)),
176 }
177}
178
179#[cfg(not(feature = "mmap"))]
180pub(crate) fn load_pack_index_data(index_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
181 Ok(Arc::new(fs::read(index_path)?))
182}
183
184#[cfg(feature = "mmap")]
185fn load_multi_pack_index_lookup_data(midx_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
186 match sley_mmap::MappedFile::open_multi_pack_index(midx_path) {
187 Ok(mapped) => Ok(Arc::new(mapped)),
188 Err(_) => Ok(Arc::new(fs::read(midx_path)?)),
189 }
190}
191
192#[cfg(not(feature = "mmap"))]
193fn load_multi_pack_index_lookup_data(midx_path: &Path) -> Result<Arc<dyn PackIndexByteSource>> {
194 Ok(Arc::new(fs::read(midx_path)?))
195}
196
197pub(crate) type DecodedObjectCache = Arc<Mutex<LruObjectCache>>;
203
204pub(crate) type PackDeltaCaches = Arc<Mutex<HashMap<PathBuf, Arc<Mutex<LruOffsetCache>>>>>;
209
210pub(crate) type PackHeaderTypeCache = Arc<Mutex<HashMap<u64, sley_pack::PackObjectHeader>>>;
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;
234
235pub(crate) fn cached_object_cost(object: &EncodedObject) -> usize {
239 object.body.len().saturating_add(64)
240}
241
242fn cache_budget_from_env(var: &str, default: usize) -> usize {
245 match env::var(var) {
246 Ok(value) => value.trim().parse::<usize>().unwrap_or(default),
247 Err(_) => default,
248 }
249}
250
251pub(crate) fn object_cache_budget() -> usize {
258 static BUDGET: OnceLock<usize> = OnceLock::new();
259 *BUDGET.get_or_init(|| {
260 cache_budget_from_env("SLEY_OBJECT_CACHE_BYTES", DEFAULT_OBJECT_CACHE_BYTES)
261 })
262}
263
264pub(crate) fn delta_base_cache_budget() -> usize {
268 static BUDGET: OnceLock<usize> = OnceLock::new();
269 *BUDGET.get_or_init(|| {
270 cache_budget_from_env(
271 "SLEY_DELTA_BASE_CACHE_BYTES",
272 DEFAULT_DELTA_BASE_CACHE_BYTES,
273 )
274 })
275}
276
277fn per_pack_delta_base_cache_budget(objects_dir: &Path, alternates: &[PathBuf]) -> usize {
278 let pack_count = std::iter::once(objects_dir)
279 .chain(alternates.iter().map(PathBuf::as_path))
280 .map(|object_dir| {
281 fs::read_dir(object_dir.join("pack"))
282 .ok()
283 .into_iter()
284 .flatten()
285 .filter_map(std::result::Result::ok)
286 .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "pack"))
287 .count()
288 })
289 .sum::<usize>()
290 .max(1);
291 delta_base_cache_budget() / pack_count
292}
293
294pub(crate) fn verify_reads_enabled() -> bool {
305 static VERIFY: OnceLock<bool> = OnceLock::new();
306 *VERIFY.get_or_init(|| match env::var("SLEY_VERIFY_READS") {
307 Ok(value) => !matches!(value.trim(), "" | "0"),
308 Err(_) => false,
309 })
310}
311
312#[derive(Debug)]
320pub(crate) struct LruCache<K: std::hash::Hash + Eq + Clone> {
321 budget: usize,
322 used: usize,
323 map: HashMap<K, LruEntry<K>>,
324 head: Option<K>,
325 tail: Option<K>,
326}
327
328#[derive(Debug)]
329struct LruEntry<K> {
330 object: Arc<EncodedObject>,
331 prev: Option<K>,
332 next: Option<K>,
333}
334
335impl<K: std::hash::Hash + Eq + Clone> LruCache<K> {
336 pub(crate) fn new(budget: usize) -> Self {
337 Self {
338 budget,
339 used: 0,
340 map: HashMap::new(),
341 head: None,
342 tail: None,
343 }
344 }
345
346 pub(crate) fn get(&mut self, key: &K) -> Option<Arc<EncodedObject>> {
347 let object = Arc::clone(&self.map.get(key)?.object);
348 self.touch(key);
349 Some(object)
350 }
351
352 pub(crate) fn touch(&mut self, key: &K) {
354 if self.tail.as_ref() == Some(key) {
355 return;
356 }
357 if self.map.contains_key(key) {
358 self.detach(key);
359 self.attach_back(key.clone());
360 }
361 }
362
363 pub(crate) fn remove(&mut self, key: &K) {
365 if let Some(entry) = self.map.get(key) {
366 self.used = self.used.saturating_sub(cached_object_cost(&entry.object));
367 }
368 self.detach(key);
369 self.map.remove(key);
370 }
371
372 pub(crate) fn detach(&mut self, key: &K) {
373 let Some((prev, next)) = self.map.get_mut(key).map(|entry| {
374 let prev = entry.prev.take();
375 let next = entry.next.take();
376 (prev, next)
377 }) else {
378 return;
379 };
380
381 match &prev {
382 Some(prev_key) => {
383 if let Some(prev_entry) = self.map.get_mut(prev_key) {
384 prev_entry.next = next.clone();
385 }
386 }
387 None => self.head = next.clone(),
388 }
389 match &next {
390 Some(next_key) => {
391 if let Some(next_entry) = self.map.get_mut(next_key) {
392 next_entry.prev = prev.clone();
393 }
394 }
395 None => self.tail = prev.clone(),
396 }
397 }
398
399 pub(crate) fn attach_back(&mut self, key: K) {
400 let previous_tail = self.tail.replace(key.clone());
401 match previous_tail {
402 Some(tail_key) => {
403 if let Some(tail_entry) = self.map.get_mut(&tail_key) {
404 tail_entry.next = Some(key.clone());
405 }
406 if let Some(entry) = self.map.get_mut(&key) {
407 entry.prev = Some(tail_key);
408 entry.next = None;
409 }
410 }
411 None => {
412 self.head = Some(key.clone());
413 if let Some(entry) = self.map.get_mut(&key) {
414 entry.prev = None;
415 entry.next = None;
416 }
417 }
418 }
419 }
420
421 pub(crate) fn clear(&mut self) {
422 self.map.clear();
423 self.head = None;
424 self.tail = None;
425 self.used = 0;
426 }
427
428 pub(crate) fn put(&mut self, key: K, object: Arc<EncodedObject>) {
429 if self.budget == 0 {
430 return;
431 }
432 let cost = cached_object_cost(&object);
433 if cost > self.budget {
437 self.remove(&key);
438 return;
439 }
440 if let Some(entry) = self.map.get_mut(&key) {
441 let previous = std::mem::replace(&mut entry.object, object);
442 self.used = self
444 .used
445 .saturating_sub(cached_object_cost(&previous))
446 .saturating_add(cost);
447 self.touch(&key);
448 } else {
449 self.used = self.used.saturating_add(cost);
450 self.map.insert(
451 key.clone(),
452 LruEntry {
453 object,
454 prev: None,
455 next: None,
456 },
457 );
458 self.attach_back(key);
459 }
460 while self.used > self.budget {
461 let Some(evicted) = self.head.clone() else {
462 break;
463 };
464 self.remove(&evicted);
465 }
466 }
467}
468
469type LruObjectCache = LruCache<ObjectId>;
471pub(crate) type LruOffsetCache = LruCache<u64>;
473
474struct PackDeltaCacheAdapter<'a>(&'a Arc<Mutex<LruOffsetCache>>);
479
480impl sley_pack::PackDeltaCache for PackDeltaCacheAdapter<'_> {
481 fn get(&self, offset: u64) -> Option<Arc<EncodedObject>> {
482 self.0.lock().ok()?.get(&offset)
483 }
484
485 fn insert(&self, offset: u64, object: Arc<EncodedObject>) {
486 if let Ok(mut cache) = self.0.lock() {
487 cache.put(offset, object);
488 }
489 }
490}
491
492struct PackHeaderTypeCacheAdapter<'a>(&'a PackHeaderTypeCache);
496
497impl sley_pack::HeaderTypeCache for PackHeaderTypeCacheAdapter<'_> {
498 fn get(&self, pack_offset: u64) -> Option<sley_pack::PackObjectHeader> {
499 self.0.lock().ok()?.get(&pack_offset).copied()
500 }
501
502 fn put(&mut self, pack_offset: u64, header: sley_pack::PackObjectHeader) {
503 if let Ok(mut cache) = self.0.lock() {
504 cache.insert(pack_offset, header);
505 }
506 }
507}
508
509const HEADER_READ_STACK_CAPACITY: usize = 64;
518const HEADER_READ_MAX_ACTIVE: usize = sley_pack::MAX_READ_DELTA_CHAIN_DEPTH + 1;
519
520struct HeaderReadContext {
521 active_oids: SmallVec<[ObjectId; HEADER_READ_STACK_CAPACITY]>,
522 delta_depth: usize,
523}
524
525impl HeaderReadContext {
526 fn new() -> Self {
527 Self {
528 active_oids: SmallVec::new(),
529 delta_depth: 0,
530 }
531 }
532
533 fn enter_pack(&mut self, oid: &ObjectId) -> Result<()> {
534 if self.active_oids.contains(oid) {
535 return Err(GitError::InvalidObject(format!(
536 "pack ref-delta cycle detected at object {oid}"
537 )));
538 }
539 if self.active_oids.len() == HEADER_READ_MAX_ACTIVE {
540 return Err(GitError::InvalidObject(format!(
541 "pack delta chain exceeds maximum depth {}",
542 sley_pack::MAX_READ_DELTA_CHAIN_DEPTH
543 )));
544 }
545 self.active_oids.push(*oid);
546 Ok(())
547 }
548
549 fn leave_pack(&mut self, oid: &ObjectId) {
550 let popped = self.active_oids.pop();
551 debug_assert_eq!(popped, Some(*oid));
552 }
553
554 fn check_ref_base(&self, oid: &ObjectId) -> Result<()> {
555 if self.active_oids.contains(oid) {
556 return Err(GitError::InvalidObject(format!(
557 "pack ref-delta cycle detected at object {oid}"
558 )));
559 }
560 if self.active_oids.len() > sley_pack::MAX_READ_DELTA_CHAIN_DEPTH {
564 return Err(GitError::InvalidObject(format!(
565 "pack delta chain exceeds maximum depth {}",
566 sley_pack::MAX_READ_DELTA_CHAIN_DEPTH
567 )));
568 }
569 Ok(())
570 }
571}
572
573pub(crate) type PackIndexCache = Arc<RwLock<HashMap<PathBuf, Arc<PackIndexViewData>>>>;
578
579pub(crate) type PackReverseIndexCache =
582 Arc<RwLock<HashMap<PathBuf, Option<Arc<PackReverseIndex>>>>>;
583
584pub(crate) type MultiPackIndexCache = Arc<RwLock<HashMap<PathBuf, Arc<MultiPackIndex>>>>;
588
589#[derive(Debug, Clone, Default, PartialEq, Eq)]
595pub struct ObjectReplacements {
596 replacements: HashMap<ObjectId, ObjectId>,
597}
598
599impl ObjectReplacements {
600 pub fn new(replacements: impl IntoIterator<Item = (ObjectId, ObjectId)>) -> Self {
601 Self {
602 replacements: replacements.into_iter().collect(),
603 }
604 }
605
606 pub fn is_empty(&self) -> bool {
607 self.replacements.is_empty()
608 }
609
610 pub fn resolve(&self, oid: &ObjectId) -> Result<ObjectId> {
613 let mut current = *oid;
614 let mut seen = HashSet::new();
615 for _ in 0..5 {
616 if !seen.insert(current) {
617 return Err(GitError::InvalidObject(format!(
618 "replace depth too high for object {oid}"
619 )));
620 }
621 let Some(next) = self.replacements.get(¤t) else {
622 return Ok(current);
623 };
624 if *next == current {
628 return Ok(current);
629 }
630 current = *next;
631 }
632 if self.replacements.contains_key(¤t) {
633 return Err(GitError::InvalidObject(format!(
634 "replace depth too high for object {oid}"
635 )));
636 }
637 Ok(current)
638 }
639}
640
641type MultiPackIndexOidLookupCache = Arc<RwLock<HashMap<PathBuf, Arc<MultiPackIndexOidLookup>>>>;
645
646#[derive(Debug, Clone)]
651pub struct FileObjectDatabase {
652 pub(crate) loose: LooseObjectStore,
653 pub(crate) objects_dir: PathBuf,
654 pub(crate) pack_dir: PathBuf,
656 pub(crate) midx_path: PathBuf,
658 pub(crate) alternates: Vec<PathBuf>,
659 pub(crate) format: ObjectFormat,
660 pub(crate) pack_bytes: PackBytesCache,
661 pub(crate) pack_indexes: PackIndexCache,
662 pub(crate) pack_reverse_indexes: PackReverseIndexCache,
663 pub(crate) multi_pack_indexes: MultiPackIndexCache,
664 pub(crate) multi_pack_oid_lookups: MultiPackIndexOidLookupCache,
665 pub(crate) pack_registry: PackRegistryCache,
666 pub(crate) decoded: DecodedObjectCache,
667 pub(crate) pack_deltas: PackDeltaCaches,
668 pub(crate) delta_base_cache_budget_per_pack: usize,
669 pub(crate) pack_header_types: PackHeaderTypeCaches,
670 pub(crate) promisor_objects: Arc<OnceLock<HashSet<ObjectId>>>,
671 pub(crate) promisor_remote_present: bool,
679 pub(crate) shallow_grafts: Arc<std::sync::OnceLock<HashSet<ObjectId>>>,
683 pub(crate) replacements: Arc<ObjectReplacements>,
684}
685
686fn pack_layout_paths(objects_dir: &Path) -> (PathBuf, PathBuf) {
690 let pack_dir = objects_dir.join("pack");
691 let midx_path = pack_dir.join("multi-pack-index");
692 (pack_dir, midx_path)
693}
694
695fn read_shallow_grafts(shallow_file: &Path, format: ObjectFormat) -> HashSet<ObjectId> {
696 let Ok(contents) = std::fs::read_to_string(shallow_file) else {
697 return HashSet::new();
698 };
699 contents
700 .lines()
701 .filter_map(|line| ObjectId::from_hex(format, line.trim()).ok())
702 .collect()
703}
704
705impl FileObjectDatabase {
706 pub fn object_format(&self) -> ObjectFormat {
708 self.format
709 }
710
711 pub fn objects_dir(&self) -> &Path {
713 &self.objects_dir
714 }
715
716 pub fn new(objects_dir: impl Into<PathBuf>, format: ObjectFormat) -> Self {
717 let objects_dir = objects_dir.into();
718 let (pack_dir, midx_path) = pack_layout_paths(&objects_dir);
719 let alternates = alternate_object_dirs(&objects_dir);
720 let delta_base_cache_budget_per_pack =
721 per_pack_delta_base_cache_budget(&objects_dir, &alternates);
722 Self {
723 loose: LooseObjectStore::new(objects_dir.clone(), format),
724 pack_dir,
725 midx_path,
726 alternates,
727 objects_dir,
728 format,
729 pack_bytes: Arc::new(RwLock::new(HashMap::new())),
730 pack_indexes: Arc::new(RwLock::new(HashMap::new())),
731 pack_reverse_indexes: Arc::new(RwLock::new(HashMap::new())),
732 multi_pack_indexes: Arc::new(RwLock::new(HashMap::new())),
733 multi_pack_oid_lookups: Arc::new(RwLock::new(HashMap::new())),
734 pack_registry: Arc::new(Mutex::new(None)),
735 decoded: Arc::new(Mutex::new(LruObjectCache::new(object_cache_budget()))),
736 pack_deltas: Arc::new(Mutex::new(HashMap::new())),
737 delta_base_cache_budget_per_pack,
738 pack_header_types: Arc::new(Mutex::new(HashMap::new())),
739 promisor_objects: Arc::new(OnceLock::new()),
740 promisor_remote_present: false,
741 shallow_grafts: Arc::new(std::sync::OnceLock::new()),
742 replacements: Arc::new(ObjectReplacements::default()),
743 }
744 }
745
746 pub(crate) fn without_alternates(
747 objects_dir: impl Into<PathBuf>,
748 format: ObjectFormat,
749 ) -> Self {
750 let objects_dir = objects_dir.into();
751 let (pack_dir, midx_path) = pack_layout_paths(&objects_dir);
752 let delta_base_cache_budget_per_pack = per_pack_delta_base_cache_budget(&objects_dir, &[]);
753 Self {
754 loose: LooseObjectStore::new(objects_dir.clone(), format),
755 pack_dir,
756 midx_path,
757 alternates: Vec::new(),
758 objects_dir,
759 format,
760 pack_bytes: Arc::new(RwLock::new(HashMap::new())),
761 pack_indexes: Arc::new(RwLock::new(HashMap::new())),
762 pack_reverse_indexes: Arc::new(RwLock::new(HashMap::new())),
763 multi_pack_indexes: Arc::new(RwLock::new(HashMap::new())),
764 multi_pack_oid_lookups: Arc::new(RwLock::new(HashMap::new())),
765 pack_registry: Arc::new(Mutex::new(None)),
766 decoded: Arc::new(Mutex::new(LruObjectCache::new(object_cache_budget()))),
767 pack_deltas: Arc::new(Mutex::new(HashMap::new())),
768 delta_base_cache_budget_per_pack,
769 pack_header_types: Arc::new(Mutex::new(HashMap::new())),
770 promisor_objects: Arc::new(OnceLock::new()),
771 promisor_remote_present: false,
772 shallow_grafts: Arc::new(std::sync::OnceLock::new()),
773 replacements: Arc::new(ObjectReplacements::default()),
774 }
775 }
776
777 pub fn from_git_dir(git_dir: impl AsRef<Path>, format: ObjectFormat) -> Self {
778 let git_dir = git_dir.as_ref();
779 let shared_repository = sley_formats::SharedRepositoryPermissions::from_git_dir(git_dir);
780 let mut database = Self::new(repository_objects_dir(git_dir), format);
781 database.loose = database.loose.with_shared_repository(shared_repository);
782 database
783 }
784
785 pub fn with_replacements(mut self, replacements: ObjectReplacements) -> Self {
788 self.replacements = Arc::new(replacements);
789 self
790 }
791
792 pub fn replacements(&self) -> &ObjectReplacements {
793 &self.replacements
794 }
795
796 pub fn replacement_oid(&self, oid: &ObjectId) -> Result<ObjectId> {
797 self.replacements.resolve(oid)
798 }
799
800 pub fn read_object_without_replacement(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
803 self.read_object_raw(oid)
804 }
805
806 pub fn read_object_header_without_replacement(
808 &self,
809 oid: &ObjectId,
810 ) -> Result<Option<(ObjectType, u64)>> {
811 self.read_object_header_raw(oid)
812 .map(|header| header.map(sley_pack::PackObjectHeader::type_and_size))
813 }
814
815 pub fn with_promisor_remote_present(mut self, present: bool) -> Self {
824 self.promisor_remote_present = present;
825 self
826 }
827
828 pub fn refresh_read_cache(&self) {
833 if let Ok(mut cache) = self.pack_registry.lock() {
834 *cache = None;
835 }
836 self.pack_indexes.write().clear();
837 self.multi_pack_indexes.write().clear();
838 self.multi_pack_oid_lookups.write().clear();
839 self.pack_bytes.write().clear();
840 if let Ok(mut cache) = self.pack_deltas.lock() {
841 cache.clear();
842 }
843 if let Ok(mut cache) = self.pack_header_types.lock() {
844 cache.clear();
845 }
846 if let Ok(mut cache) = self.decoded.lock() {
847 cache.clear();
848 }
849 self.loose.invalidate_cache();
850 }
851
852 pub fn loose(&self) -> &LooseObjectStore {
853 &self.loose
854 }
855
856 pub fn presence_checker(&self) -> ObjectPresenceChecker {
857 ObjectPresenceChecker::new(self.clone())
858 }
859
860 pub fn contains(&self, oid: &ObjectId) -> Result<bool> {
861 if self.loose.exists(oid)? {
862 return Ok(true);
863 }
864 if self.find_pack_containing(oid)?.is_some() {
865 return Ok(true);
866 }
867 for alternate in &self.alternates {
868 if Self::without_alternates(alternate, self.format).contains(oid)? {
869 return Ok(true);
870 }
871 }
872 self.loose.invalidate_cache();
875 self.loose.exists(oid)
876 }
877
878 pub fn object_ids(&self) -> Result<Vec<ObjectId>> {
879 let mut oids = object_ids_in_objects_dir(&self.objects_dir, self.format)?
880 .into_iter()
881 .collect::<HashSet<_>>();
882 for alternate in &self.alternates {
883 oids.extend(Self::without_alternates(alternate, self.format).object_ids()?);
884 }
885 let mut oids = oids.into_iter().collect::<Vec<_>>();
886 oids.sort_by_key(ObjectId::to_hex);
887 Ok(oids)
888 }
889
890 pub fn object_storage_info(&self, oid: &ObjectId) -> Result<Option<ObjectStorageInfo>> {
891 if let Some(disk_size) = self.loose.disk_size(oid)? {
892 return Ok(Some(ObjectStorageInfo {
893 disk_size,
894 deltabase: ObjectId::null(self.format),
895 }));
896 }
897 if let Some(info) = self.packed_object_storage_info(oid)? {
898 return Ok(Some(info));
899 }
900 for alternate in &self.alternates {
901 if let Some(info) =
902 Self::without_alternates(alternate, self.format).object_storage_info(oid)?
903 {
904 return Ok(Some(info));
905 }
906 }
907 self.loose.invalidate_cache();
910 if let Some(disk_size) = self.loose.disk_size(oid)? {
911 return Ok(Some(ObjectStorageInfo {
912 disk_size,
913 deltabase: ObjectId::null(self.format),
914 }));
915 }
916 Ok(None)
917 }
918
919 pub fn resolve_prefix(&self, prefix: &str) -> Result<ObjectPrefixResolution> {
920 let mut matches = self.object_ids_with_prefix(prefix)?;
921 Ok(match matches.len() {
922 0 => ObjectPrefixResolution::Missing,
923 1 => ObjectPrefixResolution::Unique(matches.remove(0)),
924 _ => ObjectPrefixResolution::Ambiguous(matches),
925 })
926 }
927
928 pub fn object_ids_with_prefix(&self, prefix: &str) -> Result<Vec<ObjectId>> {
929 validate_object_id_prefix(self.format, prefix)?;
930 let prefix_bytes = prefix.as_bytes();
931 let mut matches =
932 object_ids_with_prefix_in_objects_dir(&self.objects_dir, self.format, prefix_bytes)?
933 .into_iter()
934 .collect::<HashSet<_>>();
935 for alternate in &self.alternates {
936 for oid in
937 Self::without_alternates(alternate, self.format).object_ids_with_prefix(prefix)?
938 {
939 matches.insert(oid);
940 }
941 }
942 let mut matches = matches.into_iter().collect::<Vec<_>>();
943 matches.sort_by_key(ObjectId::to_hex);
944 Ok(matches)
945 }
946
947 pub fn read_object_header(&self, oid: &ObjectId) -> Result<Option<(ObjectType, u64)>> {
959 let read_oid = self.replacements.resolve(oid)?;
960 self.read_object_header_raw(&read_oid)
961 .map(|header| header.map(sley_pack::PackObjectHeader::type_and_size))
962 }
963
964 fn read_object_header_raw(
965 &self,
966 oid: &ObjectId,
967 ) -> Result<Option<sley_pack::PackObjectHeader>> {
968 let mut context = HeaderReadContext::new();
969 self.read_object_header_raw_with_context(oid, &mut context)
970 }
971
972 fn read_object_header_raw_with_context(
973 &self,
974 oid: &ObjectId,
975 context: &mut HeaderReadContext,
976 ) -> Result<Option<sley_pack::PackObjectHeader>> {
977 if implied_empty_tree_object(self.format, oid).is_some() {
978 return Ok(Some(sley_pack::PackObjectHeader::undeltified(
979 ObjectType::Tree,
980 0,
981 )));
982 }
983 if context.delta_depth == 0
987 && let Ok(mut cache) = self.decoded.lock()
988 && let Some(object) = cache.get(oid)
989 {
990 return Ok(Some(sley_pack::PackObjectHeader::undeltified(
991 object.object_type,
992 object.body.len() as u64,
993 )));
994 }
995 let mut first_error = None;
1000 let mut selected_pack = None;
1001 match self.find_pack_containing(oid) {
1002 Ok(Some(pack_lookup)) => {
1003 match self.read_packed_object_header_at_lookup(oid, &pack_lookup, context) {
1004 Ok(header) => return Ok(Some(header)),
1005 Err(err) => first_error = Some(err),
1006 }
1007 selected_pack = Some(pack_lookup);
1008 }
1009 Ok(None) => {}
1010 Err(err) => first_error = Some(err),
1011 }
1012
1013 if first_error.is_some() {
1017 match self.read_packed_object_header_from_pack_dir(oid, selected_pack.as_ref(), context)
1018 {
1019 Ok(Some(header)) => return Ok(Some(header)),
1020 Ok(None) => {}
1021 Err(_) => {}
1022 }
1023 }
1024
1025 match self.loose.read_header(oid) {
1026 Ok(Some((object_type, size))) => {
1027 return Ok(Some(sley_pack::PackObjectHeader::undeltified(
1028 object_type,
1029 size,
1030 )));
1031 }
1032 Ok(None) => {}
1033 Err(err) if first_error.is_none() => first_error = Some(err),
1034 Err(_) => {}
1035 }
1036 for alternate in &self.alternates {
1037 match Self::without_alternates(alternate, self.format)
1038 .read_object_header_raw_with_context(oid, context)
1039 {
1040 Ok(Some(header)) => return Ok(Some(header)),
1041 Ok(None) => {}
1042 Err(err) if first_error.is_none() => first_error = Some(err),
1043 Err(_) => {}
1044 }
1045 }
1046 self.loose.invalidate_cache();
1049 match self.loose.read_header(oid) {
1050 Ok(Some((object_type, size))) => {
1051 return Ok(Some(sley_pack::PackObjectHeader::undeltified(
1052 object_type,
1053 size,
1054 )));
1055 }
1056 Ok(None) => {}
1057 Err(err) if first_error.is_none() => first_error = Some(err),
1058 Err(_) => {}
1059 }
1060 if let Some(err) = first_error {
1061 return Err(err);
1062 }
1063 Ok(None)
1064 }
1065
1066 fn read_packed_object_header_at_lookup(
1067 &self,
1068 oid: &ObjectId,
1069 pack_lookup: &PackLookup,
1070 context: &mut HeaderReadContext,
1071 ) -> Result<sley_pack::PackObjectHeader> {
1072 context.enter_pack(oid)?;
1073 let result = (|| {
1074 let bytes = pack_lookup.pack_bytes(self)?;
1075 let type_cache = pack_lookup.header_type_cache(self);
1081 let initial_delta_depth = context.delta_depth;
1082 let resolve_ref_base = |base: &ObjectId, delta_depth: usize| {
1083 context.check_ref_base(base)?;
1084 let previous_delta_depth = context.delta_depth;
1085 context.delta_depth = delta_depth;
1086 let result = self.read_object_header_raw_with_context(base, context);
1087 context.delta_depth = previous_delta_depth;
1088 result
1089 };
1090 match &type_cache {
1091 Some(cache) => {
1092 let mut adapter = PackHeaderTypeCacheAdapter(cache);
1093 sley_pack::read_object_header_at_with_cache(
1094 &bytes,
1095 pack_lookup.offset,
1096 self.format,
1097 initial_delta_depth,
1098 resolve_ref_base,
1099 &mut adapter,
1100 )
1101 }
1102 None => sley_pack::read_object_header_at(
1103 &bytes,
1104 pack_lookup.offset,
1105 self.format,
1106 initial_delta_depth,
1107 resolve_ref_base,
1108 ),
1109 }
1110 })();
1111 context.leave_pack(oid);
1112 result
1113 }
1114
1115 fn read_packed_object_header_from_pack_dir(
1116 &self,
1117 oid: &ObjectId,
1118 exclude: Option<&PackLookup>,
1119 context: &mut HeaderReadContext,
1120 ) -> Result<Option<sley_pack::PackObjectHeader>> {
1121 let Ok(entries) = fs::read_dir(&self.pack_dir) else {
1122 return Ok(None);
1123 };
1124 let excluded_pack = exclude.map(|lookup| lookup.pack_path());
1125 for entry in entries {
1126 let idx_path = entry?.path();
1127 if idx_path.extension().and_then(|ext| ext.to_str()) != Some("idx") {
1128 continue;
1129 }
1130 let pack_path = idx_path.with_extension("pack");
1131 if excluded_pack == Some(pack_path.as_path()) {
1132 continue;
1133 }
1134 let Ok(idx_bytes) = fs::read(&idx_path) else {
1135 continue;
1136 };
1137 let Ok(index) = PackIndex::parse(&idx_bytes, self.format) else {
1138 continue;
1139 };
1140 let Some(entry) = index.find(oid) else {
1141 continue;
1142 };
1143 let candidate = PackLookup::from_path(pack_path, entry.offset);
1144 if let Ok(header) = self.read_packed_object_header_at_lookup(oid, &candidate, context) {
1145 return Ok(Some(header));
1146 }
1147 }
1148 Ok(None)
1149 }
1150
1151 pub(crate) fn read_packed_object(&self, oid: &ObjectId) -> Result<Option<Arc<EncodedObject>>> {
1152 if let Ok(mut cache) = self.decoded.lock()
1155 && let Some(object) = cache.get(oid)
1156 {
1157 return Ok(Some(object));
1158 }
1159 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1160 return Ok(None);
1161 };
1162 self.read_packed_object_at_lookup(oid, &pack_lookup)
1163 .map(Some)
1164 }
1165
1166 pub(crate) fn read_packed_object_at_lookup(
1167 &self,
1168 oid: &ObjectId,
1169 pack_lookup: &PackLookup,
1170 ) -> Result<Arc<EncodedObject>> {
1171 if let Ok(mut cache) = self.decoded.lock()
1172 && let Some(object) = cache.get(oid)
1173 {
1174 return Ok(object);
1175 }
1176 let bytes = pack_lookup.pack_bytes(self)?;
1177 let delta_cache = pack_lookup.delta_cache(self);
1182 let delta_adapter = delta_cache.as_ref().map(PackDeltaCacheAdapter);
1183 let resolve_ref_base = |base: &ObjectId| self.read_object_raw(base).map(Some);
1189 let resolve_ofs_base =
1190 |base_offset| self.read_ofs_delta_base_from_other_sources(pack_lookup, base_offset);
1191 let object = match &delta_adapter {
1192 Some(adapter) => sley_pack::read_object_at_with_cache_and_ofs_base_arc(
1193 &bytes,
1194 pack_lookup.offset,
1195 self.format,
1196 resolve_ref_base,
1197 resolve_ofs_base,
1198 adapter,
1199 )?,
1200 None => sley_pack::read_object_at_with_ofs_base_arc(
1201 &bytes,
1202 pack_lookup.offset,
1203 self.format,
1204 resolve_ref_base,
1205 resolve_ofs_base,
1206 )?,
1207 };
1208 if verify_reads_enabled() {
1212 let actual = object.object_id(self.format)?;
1213 if actual != *oid {
1214 return Err(GitError::InvalidObject(format!(
1215 "pack object id mismatch: index says {oid}, decoded {actual}"
1216 )));
1217 }
1218 }
1219 if let Ok(mut cache) = self.decoded.lock() {
1220 cache.put(*oid, Arc::clone(&object));
1221 }
1222 Ok(object)
1223 }
1224
1225 pub(crate) fn pack_delta_cache(&self, pack_path: &Path) -> Option<Arc<Mutex<LruOffsetCache>>> {
1229 let mut caches = self.pack_deltas.lock().ok()?;
1230 let cache = caches.entry(pack_path.to_path_buf()).or_insert_with(|| {
1231 Arc::new(Mutex::new(LruOffsetCache::new(
1232 self.delta_base_cache_budget_per_pack,
1233 )))
1234 });
1235 Some(Arc::clone(cache))
1236 }
1237
1238 pub(crate) fn pack_header_type_cache(&self, pack_path: &Path) -> Option<PackHeaderTypeCache> {
1242 let mut caches = self.pack_header_types.lock().ok()?;
1243 let cache = caches
1244 .entry(pack_path.to_path_buf())
1245 .or_insert_with(|| Arc::new(Mutex::new(HashMap::new())));
1246 Some(Arc::clone(cache))
1247 }
1248
1249 pub(crate) fn cached_pack_bytes(&self, pack_path: &Path) -> Result<Arc<PackData>> {
1254 if let Some(bytes) = self.pack_bytes.read().get(pack_path) {
1255 return Ok(Arc::clone(bytes));
1256 }
1257 let bytes = Arc::new(load_pack_data(pack_path)?);
1258 self.pack_bytes
1259 .write()
1260 .insert(pack_path.to_path_buf(), Arc::clone(&bytes));
1261 Ok(bytes)
1262 }
1263
1264 pub(crate) fn cached_pack_index(&self, index_path: &Path) -> Result<Arc<PackIndexViewData>> {
1268 if let Some(index) = self.pack_indexes.read().get(index_path) {
1269 return Ok(Arc::clone(index));
1270 }
1271 let index_bytes = load_pack_index_data(index_path)?;
1272 let index = Arc::new(PackIndexViewData::parse_trusted_source_without_checksum(
1273 index_bytes,
1274 self.format,
1275 )?);
1276 self.pack_indexes
1277 .write()
1278 .insert(index_path.to_path_buf(), Arc::clone(&index));
1279 Ok(index)
1280 }
1281
1282 pub(crate) fn cached_pack_reverse_index(
1285 &self,
1286 index_path: &Path,
1287 index: &PackIndexViewData,
1288 ) -> Result<Option<Arc<PackReverseIndex>>> {
1289 if let Some(cached) = self.pack_reverse_indexes.read().get(index_path) {
1290 return Ok(cached.as_ref().map(Arc::clone));
1291 }
1292 let rev_path = index_path.with_extension("rev");
1293 let reverse = if rev_path.exists() {
1294 let bytes = fs::read(&rev_path)?;
1295 match PackReverseIndex::parse(&bytes, self.format, index.count) {
1296 Ok(parsed) if parsed.pack_checksum == index.pack_checksum => Some(Arc::new(parsed)),
1297 _ => None,
1298 }
1299 } else {
1300 None
1301 };
1302 self.pack_reverse_indexes
1303 .write()
1304 .insert(index_path.to_path_buf(), reverse.as_ref().map(Arc::clone));
1305 Ok(reverse)
1306 }
1307
1308 pub(crate) fn cached_multi_pack_index_oid_lookup(
1309 &self,
1310 midx_path: &Path,
1311 ) -> Result<Option<Arc<MultiPackIndexOidLookup>>> {
1312 if !midx_path.exists() {
1313 return Ok(None);
1314 }
1315 if let Some(midx) = self.multi_pack_oid_lookups.read().get(midx_path) {
1316 return Ok(Some(Arc::clone(midx)));
1317 }
1318 let bytes = load_multi_pack_index_lookup_data(midx_path)?;
1319 let midx = match MultiPackIndexOidLookup::parse(bytes, self.format) {
1320 Ok(midx) => Arc::new(midx),
1321 Err(GitError::InvalidFormat(message))
1322 if message.starts_with("multi-pack-index hash id ") =>
1323 {
1324 let actual = message
1325 .strip_prefix("multi-pack-index hash id ")
1326 .and_then(|rest| rest.split_whitespace().next())
1327 .unwrap_or("0");
1328 let expected = match self.format {
1329 ObjectFormat::Sha1 => 1,
1330 ObjectFormat::Sha256 => 2,
1331 };
1332 eprintln!(
1333 "error: multi-pack-index hash version {actual} does not match version {expected}"
1334 );
1335 return Ok(None);
1336 }
1337 Err(err) => return Err(err),
1338 };
1339 self.multi_pack_oid_lookups
1340 .write()
1341 .insert(midx_path.to_path_buf(), Arc::clone(&midx));
1342 Ok(Some(midx))
1343 }
1344
1345 pub(crate) fn cached_multi_pack_index(
1346 &self,
1347 midx_path: &Path,
1348 ) -> Result<Option<Arc<MultiPackIndex>>> {
1349 if !midx_path.exists() {
1350 return Ok(None);
1351 }
1352 if let Some(midx) = self.multi_pack_indexes.read().get(midx_path) {
1353 return Ok(Some(Arc::clone(midx)));
1354 }
1355 let bytes = load_multi_pack_index_lookup_data(midx_path)?;
1356 let midx = match MultiPackIndex::parse(bytes.as_bytes(), self.format) {
1357 Ok(midx) => Arc::new(midx),
1358 Err(GitError::InvalidFormat(message))
1359 if message.starts_with("multi-pack-index hash id ") =>
1360 {
1361 let actual = message
1362 .strip_prefix("multi-pack-index hash id ")
1363 .and_then(|rest| rest.split_whitespace().next())
1364 .unwrap_or("0");
1365 let expected = match self.format {
1366 ObjectFormat::Sha1 => 1,
1367 ObjectFormat::Sha256 => 2,
1368 };
1369 eprintln!(
1370 "error: multi-pack-index hash version {actual} does not match version {expected}"
1371 );
1372 return Ok(None);
1373 }
1374 Err(err) => return Err(err),
1375 };
1376 self.multi_pack_indexes
1377 .write()
1378 .insert(midx_path.to_path_buf(), Arc::clone(&midx));
1379 Ok(Some(midx))
1380 }
1381
1382 pub(crate) fn cached_pack_registry(
1387 &self,
1388 pack_dir: &Path,
1389 force_rescan: bool,
1390 ) -> Result<Arc<PackRegistrySnapshot>> {
1391 if !force_rescan && let Some(registry) = self.cached_loaded_pack_registry(pack_dir)? {
1392 return Ok(registry);
1393 }
1394 let scanned = Arc::new(scan_pack_registry(
1395 pack_dir,
1396 self.format,
1397 self.delta_base_cache_budget_per_pack,
1398 )?);
1399 if let Ok(mut cache) = self.pack_registry.lock() {
1400 match cache.as_ref() {
1401 Some(existing)
1402 if existing.fingerprint == scanned.fingerprint
1403 && same_registered_pack_set(&existing.packs, &scanned.packs) =>
1404 {
1405 return Ok(Arc::clone(existing));
1406 }
1407 _ => {
1408 *cache = Some(Arc::clone(&scanned));
1409 }
1410 }
1411 }
1412 Ok(scanned)
1413 }
1414
1415 pub(crate) fn find_in_pack_registry(
1416 &self,
1417 registry: Arc<PackRegistrySnapshot>,
1418 oid: &ObjectId,
1419 ) -> Result<Option<PackLookup>> {
1420 let hinted_pack_index = registry.cached_hint();
1421 if let Some(pack_index) = hinted_pack_index {
1422 let pack = ®istry.packs[pack_index];
1423 match pack.index(self.format) {
1424 Ok(index) => {
1425 if let Some(entry) = index.find(oid) {
1426 return Ok(Some(PackLookup::from_registered(
1427 Arc::clone(pack),
1428 entry.offset,
1429 )));
1430 }
1431 }
1432 Err(_) => {
1433 eprintln!("error: packfile {} index unavailable", pack.pack.display());
1434 }
1435 }
1436 }
1437 for (pack_index, pack) in registry.packs.iter().enumerate() {
1438 if Some(pack_index) == hinted_pack_index {
1439 continue;
1440 }
1441 let index = match pack.index(self.format) {
1442 Ok(index) => index,
1443 Err(_) => {
1444 eprintln!("error: packfile {} index unavailable", pack.pack.display());
1445 continue;
1446 }
1447 };
1448 if let Some(entry) = index.find(oid) {
1449 registry.remember_hint(pack_index);
1450 return Ok(Some(PackLookup::from_registered(
1451 Arc::clone(pack),
1452 entry.offset,
1453 )));
1454 }
1455 }
1456 Ok(None)
1457 }
1458
1459 pub(crate) fn read_packed_object_from_other_packs(
1465 &self,
1466 oid: &ObjectId,
1467 exclude: &PackLookup,
1468 ) -> Result<Option<Arc<EncodedObject>>> {
1469 let pack_dir = self.objects_dir.join("pack");
1470 let Ok(entries) = fs::read_dir(&pack_dir) else {
1471 return Ok(None);
1472 };
1473 let excluded_pack = exclude.pack_path().to_path_buf();
1474 for entry in entries {
1475 let idx_path = entry?.path();
1476 if idx_path.extension().and_then(|ext| ext.to_str()) != Some("idx") {
1477 continue;
1478 }
1479 let pack_path = idx_path.with_extension("pack");
1480 if pack_path == excluded_pack {
1481 continue;
1482 }
1483 let Ok(idx_bytes) = fs::read(&idx_path) else {
1484 continue;
1485 };
1486 let Ok(index) = PackIndex::parse(&idx_bytes, self.format) else {
1487 continue;
1488 };
1489 let Some(entry) = index.find(oid) else {
1490 continue;
1491 };
1492 let candidate = PackLookup::from_path(pack_path, entry.offset);
1493 if let Ok(object) = self.read_packed_object_at_lookup(oid, &candidate) {
1494 return Ok(Some(object));
1495 }
1496 }
1497 Ok(None)
1498 }
1499
1500 pub(crate) fn pack_oid_at_offset(
1501 &self,
1502 pack_lookup: &PackLookup,
1503 offset: u64,
1504 ) -> Result<Option<ObjectId>> {
1505 let index_path = match &pack_lookup.registered {
1506 Some(pack) => pack.idx.clone(),
1507 None => pack_lookup.pack.with_extension("idx"),
1508 };
1509 match pack_lookup.pack_index(self) {
1510 Ok(index) => {
1511 if let Some(reverse) = self.cached_pack_reverse_index(&index_path, &index)? {
1512 Ok(reverse.oid_at_offset(&index, offset))
1513 } else {
1514 Ok(index.oid_at_offset_linear(offset))
1515 }
1516 }
1517 Err(_) => self.midx_oid_for_pack_offset(pack_lookup, offset),
1518 }
1519 }
1520
1521 pub(crate) fn read_ofs_delta_base_from_other_sources(
1522 &self,
1523 pack_lookup: &PackLookup,
1524 base_offset: u64,
1525 ) -> Result<Option<Arc<EncodedObject>>> {
1526 let Some(base_oid) = self.pack_oid_at_offset(pack_lookup, base_offset)? else {
1527 return Ok(None);
1528 };
1529 if let Ok(mut cache) = self.decoded.lock()
1530 && let Some(object) = cache.get(&base_oid)
1531 {
1532 return Ok(Some(object));
1533 }
1534 if let Ok(object) = self.loose.read_object(&base_oid) {
1535 return Ok(Some(object));
1536 }
1537 if let Some(object) = self.read_packed_object_from_other_packs(&base_oid, pack_lookup)? {
1538 return Ok(Some(object));
1539 }
1540 for alternate in &self.alternates {
1541 if let Ok(object) =
1542 Self::without_alternates(alternate, self.format).read_object(&base_oid)
1543 {
1544 return Ok(Some(object));
1545 }
1546 }
1547 Ok(None)
1548 }
1549
1550 pub(crate) fn find_pack_containing(&self, oid: &ObjectId) -> Result<Option<PackLookup>> {
1551 if oid.format() != self.format {
1552 return Err(GitError::InvalidObjectId(format!(
1553 "object {oid} uses {}, store uses {}",
1554 oid.format().name(),
1555 self.format.name()
1556 )));
1557 }
1558 let pack_dir = &self.pack_dir;
1559 if let Some(midx) = self.cached_loaded_multi_pack_index_oid_lookup()
1564 && let Some(pack_paths) = self.midx_oid_lookup_pack_paths(pack_dir, &midx, oid)?
1565 {
1566 return Ok(Some(pack_paths));
1567 }
1568 if let Some(registry) = self.cached_loaded_pack_registry(pack_dir)?
1569 && let Some(pack_paths) = self.find_in_pack_registry(registry, oid)?
1570 {
1571 return Ok(Some(pack_paths));
1572 }
1573
1574 if !pack_dir.exists() {
1575 return Ok(None);
1576 }
1577 if let Some(pack_paths) = self.find_midx_pack_containing(pack_dir, oid)? {
1578 return Ok(Some(pack_paths));
1579 }
1580 let registry = self.cached_pack_registry(pack_dir, false)?;
1584 if let Some(pack_paths) = self.find_in_pack_registry(Arc::clone(®istry), oid)? {
1585 return Ok(Some(pack_paths));
1586 }
1587 let refreshed = self.cached_pack_registry(pack_dir, true)?;
1588 if Arc::ptr_eq(®istry, &refreshed) {
1589 return Ok(None);
1591 }
1592 self.find_in_pack_registry(refreshed, oid)
1593 }
1594
1595 pub(crate) fn packed_object_storage_info(
1596 &self,
1597 oid: &ObjectId,
1598 ) -> Result<Option<ObjectStorageInfo>> {
1599 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1600 return Ok(None);
1601 };
1602 let index = pack_lookup.pack_index(self).ok();
1603 let pack = match pack_lookup.pack_bytes(self) {
1604 Ok(pack) => Some(pack),
1605 Err(_err) if index.is_some() => None,
1606 Err(err) => return Err(err),
1607 };
1608 let trailer_offset = pack
1609 .as_ref()
1610 .map(|pack| {
1611 (pack.len() as u64)
1612 .checked_sub(self.format.raw_len() as u64)
1613 .ok_or_else(|| {
1614 GitError::InvalidFormat("pack file shorter than checksum".into())
1615 })
1616 })
1617 .transpose()?;
1618 let delta_base = match &pack {
1619 Some(pack) => pack_entry_delta_base(self.format, pack, pack_lookup.offset)?,
1620 None => None,
1621 };
1622 let delta_base_offset = match &delta_base {
1623 Some(PackDeltaBase::Offset(offset)) => Some(*offset),
1624 Some(PackDeltaBase::Ref(_)) | None => None,
1625 };
1626 let offset_info = if let Some(index) = &index {
1627 scan_pack_index_offsets(index, pack_lookup.offset, trailer_offset, delta_base_offset)?
1628 } else if let Some(pack) = &pack {
1629 let end_offset =
1630 scan_pack_offsets_without_index(self.format, pack, pack_lookup.offset)?
1631 .ok_or_else(|| {
1632 GitError::InvalidFormat(format!(
1633 "pack offset {} not found",
1634 pack_lookup.offset
1635 ))
1636 })?;
1637 let delta_base_oid = match delta_base_offset {
1638 Some(offset) => self
1639 .midx_oid_for_pack_offset(&pack_lookup, offset)?
1640 .ok_or_else(|| {
1641 GitError::InvalidFormat(format!("ofs-delta base offset {offset} not found"))
1642 })?,
1643 None => ObjectId::null(self.format),
1644 };
1645 PackIndexOffsetInfo {
1646 end_offset,
1647 delta_base_oid: delta_base_offset.map(|_| delta_base_oid),
1648 }
1649 } else {
1650 return Err(GitError::InvalidFormat(
1651 "packed object metadata source unavailable".into(),
1652 ));
1653 };
1654 let disk_size = offset_info
1655 .end_offset
1656 .checked_sub(pack_lookup.offset)
1657 .ok_or_else(|| GitError::InvalidFormat("pack index offsets are not sorted".into()))?;
1658 let deltabase = match delta_base {
1659 Some(PackDeltaBase::Offset(_)) => offset_info.delta_base_oid.ok_or_else(|| {
1660 GitError::InvalidFormat("ofs-delta base oid missing from pack index".into())
1666 })?,
1667 Some(PackDeltaBase::Ref(oid)) => oid,
1668 None => ObjectId::null(self.format),
1669 };
1670 Ok(Some(ObjectStorageInfo {
1671 disk_size,
1672 deltabase,
1673 }))
1674 }
1675
1676 pub(crate) fn midx_oid_for_pack_offset(
1677 &self,
1678 pack_lookup: &PackLookup,
1679 offset: u64,
1680 ) -> Result<Option<ObjectId>> {
1681 let pack_dir = self.objects_dir.join("pack");
1682 let midx_path = pack_dir.join("multi-pack-index");
1683 let Some(midx) = self.cached_multi_pack_index(&midx_path)? else {
1684 return Ok(None);
1685 };
1686 let Some(pack_name) = pack_lookup
1687 .pack_path()
1688 .file_name()
1689 .and_then(|name| name.to_str())
1690 else {
1691 return Ok(None);
1692 };
1693 let idx_name = pack_name
1694 .strip_suffix(".pack")
1695 .map(|stem| format!("{stem}.idx"))
1696 .unwrap_or_else(|| pack_name.to_string());
1697 let Some(pack_int_id) = midx
1698 .pack_names
1699 .iter()
1700 .position(|candidate| candidate == &idx_name)
1701 else {
1702 return Ok(None);
1703 };
1704 Ok(midx
1705 .objects
1706 .iter()
1707 .find(|entry| entry.pack_int_id == pack_int_id as u32 && entry.offset == offset)
1708 .map(|entry| entry.oid))
1709 }
1710
1711 pub(crate) fn find_midx_pack_containing(
1712 &self,
1713 pack_dir: &Path,
1714 oid: &ObjectId,
1715 ) -> Result<Option<PackLookup>> {
1716 let midx_path = pack_dir.join("multi-pack-index");
1717 if let Some(midx) = self.cached_multi_pack_index_oid_lookup(&midx_path)?
1718 && let Some(pack_lookup) = self.midx_oid_lookup_pack_paths(pack_dir, &midx, oid)?
1719 {
1720 return Ok(Some(pack_lookup));
1721 }
1722 self.find_incremental_midx_pack_containing(pack_dir, oid)
1723 }
1724
1725 pub(crate) fn midx_oid_lookup_pack_paths(
1726 &self,
1727 pack_dir: &Path,
1728 midx: &MultiPackIndexOidLookup,
1729 oid: &ObjectId,
1730 ) -> Result<Option<PackLookup>> {
1731 let Some(entry) = midx.find(oid)? else {
1732 return Ok(None);
1733 };
1734 let Some(pack_name) = midx.pack_name(entry.pack_int_id) else {
1735 return Err(GitError::InvalidFormat(
1736 "multi-pack-index object points past pack table".into(),
1737 ));
1738 };
1739 let pack_file_name = pack_name
1740 .strip_suffix(".idx")
1741 .map(|stem| format!("{stem}.pack"))
1742 .unwrap_or_else(|| pack_name.to_string());
1743 let pack = pack_dir.join(pack_file_name);
1744 Ok(Some(PackLookup::from_path(pack, entry.offset)))
1745 }
1746
1747 pub(crate) fn find_incremental_midx_pack_containing(
1748 &self,
1749 pack_dir: &Path,
1750 oid: &ObjectId,
1751 ) -> Result<Option<PackLookup>> {
1752 let chain = read_incremental_midx_chain(pack_dir)?;
1753 if chain.is_empty() {
1754 return Ok(None);
1755 }
1756 let midx_dir = pack_dir.join("multi-pack-index.d");
1757 for checksum in chain.iter().rev() {
1758 let path = midx_dir.join(format!("multi-pack-index-{checksum}.midx"));
1759 if !path.exists() {
1760 continue;
1761 }
1762 let bytes = load_multi_pack_index_lookup_data(&path)?;
1763 let midx = match MultiPackIndexOidLookup::parse(bytes, self.format) {
1764 Ok(midx) => midx,
1765 Err(_) => continue,
1766 };
1767 if let Some(pack_lookup) = self.midx_oid_lookup_pack_paths(pack_dir, &midx, oid)? {
1768 return Ok(Some(pack_lookup));
1769 }
1770 }
1771 Ok(None)
1772 }
1773
1774 pub(crate) fn cached_loaded_multi_pack_index_oid_lookup(
1775 &self,
1776 ) -> Option<Arc<MultiPackIndexOidLookup>> {
1777 self.multi_pack_oid_lookups
1778 .read()
1779 .get(&self.midx_path)
1780 .map(Arc::clone)
1781 }
1782
1783 pub(crate) fn cached_loaded_pack_registry(
1789 &self,
1790 _pack_dir: &Path,
1791 ) -> Result<Option<Arc<PackRegistrySnapshot>>> {
1792 let cache = match self.pack_registry.lock() {
1793 Ok(cache) => cache,
1794 Err(_) => return Ok(None),
1795 };
1796 Ok(cache.as_ref().map(Arc::clone))
1797 }
1798}
1799impl ObjectReader for FileObjectDatabase {
1800 fn reusable_pack_candidates(
1801 &self,
1802 object_ids: &HashSet<ObjectId>,
1803 ) -> Result<Vec<ReusablePackCandidate>> {
1804 if object_ids.is_empty() || !self.replacements.is_empty() {
1808 return Ok(Vec::new());
1809 }
1810
1811 let mut object_dirs = Vec::with_capacity(self.alternates.len() + 1);
1812 object_dirs.push(self.objects_dir.clone());
1813 object_dirs.extend(self.alternates.iter().cloned());
1814 let mut candidates = Vec::new();
1815 for objects_dir in object_dirs {
1816 let pack_dir = objects_dir.join("pack");
1817 let Ok(entries) = fs::read_dir(pack_dir) else {
1818 continue;
1819 };
1820 let mut index_paths = Vec::new();
1821 for entry in entries.flatten() {
1822 let path = entry.path();
1823 if path.extension().and_then(|ext| ext.to_str()) == Some("idx") {
1824 index_paths.push(path);
1825 }
1826 }
1827 index_paths.sort();
1828 for index_path in index_paths {
1829 let Ok(index_bytes) = fs::read(&index_path) else {
1830 continue;
1831 };
1832 let Ok(index) = PackIndex::parse(&index_bytes, self.format) else {
1833 continue;
1834 };
1835 if !index
1836 .entries
1837 .iter()
1838 .any(|entry| object_ids.contains(&entry.oid))
1839 {
1840 continue;
1841 }
1842 candidates.push(ReusablePackCandidate {
1843 pack: Arc::from([]),
1844 pack_path: Some(index_path.with_extension("pack")),
1845 entries: index.entries,
1846 pack_checksum: index.pack_checksum,
1847 });
1848 }
1849 }
1850 Ok(candidates)
1851 }
1852
1853 fn reusable_delta_base(&self, oid: &ObjectId) -> Result<Option<ObjectId>> {
1854 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
1855 return Ok(None);
1856 };
1857 let pack = pack_lookup.pack_bytes(self)?;
1858 match pack_entry_delta_base(self.format, &pack, pack_lookup.offset)? {
1859 Some(PackDeltaBase::Ref(base_oid)) => Ok(Some(base_oid)),
1860 Some(PackDeltaBase::Offset(base_offset)) => {
1861 let base_oid = self
1862 .pack_oid_at_offset(&pack_lookup, base_offset)?
1863 .ok_or_else(|| {
1864 GitError::InvalidFormat(format!(
1865 "ofs-delta base offset {base_offset} not found"
1866 ))
1867 })?;
1868 Ok(Some(base_oid))
1869 }
1870 None => Ok(None),
1871 }
1872 }
1873
1874 fn is_promised_object(&self, oid: &ObjectId) -> bool {
1875 self.promisor_remote_present && self.promisor_objects().contains(oid)
1881 }
1882
1883 fn has_shallow_grafts(&self) -> bool {
1884 !self
1885 .shallow_grafts
1886 .get_or_init(|| {
1887 let shallow_file = self
1888 .objects_dir
1889 .parent()
1890 .map(|git_dir| git_dir.join("shallow"));
1891 match shallow_file {
1892 Some(path) => read_shallow_grafts(&path, self.format),
1893 None => HashSet::new(),
1894 }
1895 })
1896 .is_empty()
1897 }
1898
1899 fn is_shallow_graft(&self, oid: &ObjectId) -> bool {
1900 self.shallow_grafts
1901 .get_or_init(|| {
1902 let shallow_file = self
1903 .objects_dir
1904 .parent()
1905 .map(|git_dir| git_dir.join("shallow"));
1906 match shallow_file {
1907 Some(path) => read_shallow_grafts(&path, self.format),
1908 None => HashSet::new(),
1909 }
1910 })
1911 .contains(oid)
1912 }
1913
1914 fn read_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
1915 let read_oid = self.replacements.resolve(oid)?;
1916 self.read_object_raw(&read_oid)
1917 }
1918}
1919
1920impl sley_formats::TreeObjectSource for FileObjectDatabase {
1921 fn read_tree_object_header(&self, oid: &ObjectId) -> Result<Option<(ObjectType, u64)>> {
1922 self.read_object_header(oid)
1923 }
1924
1925 fn read_tree_object(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
1926 ObjectReader::read_object(self, oid)
1927 }
1928}
1929
1930impl FileObjectDatabase {
1931 fn read_object_raw(&self, oid: &ObjectId) -> Result<Arc<EncodedObject>> {
1932 if let Some(object) = implied_empty_tree_object(self.format, oid) {
1933 return Ok(object);
1934 }
1935 if let Some(pack_lookup) = self.find_pack_containing(oid)? {
1943 match self.read_packed_object_at_lookup(oid, &pack_lookup) {
1944 Ok(object) => return Ok(object),
1945 Err(GitError::NotFound(_)) => {}
1946 Err(packed_err) => {
1952 if let Ok(object) = self.loose.read_object(oid) {
1953 return Ok(object);
1954 }
1955 if let Some(object) =
1958 self.read_packed_object_from_other_packs(oid, &pack_lookup)?
1959 {
1960 return Ok(object);
1961 }
1962 for alternate in &self.alternates {
1963 if let Ok(object) =
1964 Self::without_alternates(alternate, self.format).read_object_raw(oid)
1965 {
1966 return Ok(object);
1967 }
1968 }
1969 return Err(packed_err);
1970 }
1971 }
1972 }
1973 let loose_err = match self.loose.read_object(oid) {
1974 Ok(object) => return Ok(object),
1975 Err(GitError::NotFound(_)) => None,
1976 Err(err) => Some(err),
1977 };
1978 if let Some(object) = self.read_packed_object(oid)? {
1979 return Ok(object);
1980 }
1981 for alternate in &self.alternates {
1982 match Self::without_alternates(alternate, self.format).read_object_raw(oid) {
1983 Ok(object) => return Ok(object),
1984 Err(GitError::NotFound(_)) => {}
1985 Err(err) => return Err(err),
1986 }
1987 }
1988 self.loose.invalidate_cache();
1994 match self.loose.read_object(oid) {
1995 Ok(object) => return Ok(object),
1996 Err(GitError::NotFound(_)) => {}
1997 Err(err) => return Err(err),
1998 }
1999 if let Some(err) = loose_err {
2003 return Err(err);
2004 }
2005 Err(GitError::object_not_found_in(
2006 *oid,
2007 MissingObjectContext::Read,
2008 ))
2009 }
2010}
2011impl FileObjectDatabase {
2012 fn promisor_objects(&self) -> &HashSet<ObjectId> {
2013 self.promisor_objects.get_or_init(|| {
2014 let mut promised =
2021 promisor_pack_object_ids(&self.objects_dir, self.format).unwrap_or_default();
2022 for alternate in &self.alternates {
2023 if let Ok(oids) = promisor_pack_object_ids(alternate, self.format) {
2024 promised.extend(oids);
2025 }
2026 }
2027 let mut pending = promised.iter().copied().collect::<Vec<_>>();
2028 while let Some(oid) = pending.pop() {
2029 let Ok(object) = self.read_object(&oid) else {
2030 continue;
2031 };
2032 for link in promisor_object_links(self.format, &object) {
2033 if promised.insert(link) {
2034 pending.push(link);
2035 }
2036 }
2037 }
2038 promised
2039 })
2040 }
2041
2042 fn freshen_existing_object(&self, oid: &ObjectId) -> Result<bool> {
2043 if self.freshen_loose_object(oid)? {
2044 return Ok(true);
2045 }
2046 if self.freshen_packed_object(oid)? {
2047 return Ok(true);
2048 }
2049 for alternate in &self.alternates {
2050 if Self::without_alternates(alternate, self.format).freshen_existing_object(oid)? {
2051 return Ok(true);
2052 }
2053 }
2054 self.loose.invalidate_cache();
2056 self.freshen_loose_object(oid)
2057 }
2058
2059 fn freshen_loose_object(&self, oid: &ObjectId) -> Result<bool> {
2060 let path = self.loose.object_path(oid)?;
2061 freshen_file_mtime(&path)
2062 }
2063
2064 fn freshen_packed_object(&self, oid: &ObjectId) -> Result<bool> {
2065 let Some(pack_lookup) = self.find_pack_containing(oid)? else {
2066 return Ok(false);
2067 };
2068 if !pack_lookup.pack_path().with_extension("mtimes").exists() {
2069 return freshen_file_mtime(pack_lookup.pack_path());
2070 }
2071
2072 let pack_dir = self.objects_dir.join("pack");
2078 let Ok(entries) = fs::read_dir(pack_dir) else {
2079 return Ok(false);
2080 };
2081 for entry in entries {
2082 let idx_path = entry?.path();
2083 if idx_path.extension().and_then(|ext| ext.to_str()) != Some("idx")
2084 || idx_path.with_extension("mtimes").exists()
2085 {
2086 continue;
2087 }
2088 let pack_path = idx_path.with_extension("pack");
2089 if !pack_path.exists() {
2090 continue;
2091 }
2092 let Ok(index_bytes) = fs::read(&idx_path) else {
2093 continue;
2094 };
2095 let Ok(index) = PackIndex::parse(&index_bytes, self.format) else {
2096 continue;
2097 };
2098 if index.find(oid).is_some() {
2099 return freshen_file_mtime(&pack_path);
2100 }
2101 }
2102 Ok(false)
2103 }
2104}
2105pub(crate) fn freshen_file_mtime(path: &Path) -> Result<bool> {
2106 let file = match fs::OpenOptions::new().read(true).open(path) {
2107 Ok(file) => file,
2108 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
2109 Err(err) => return Err(GitError::Io(err.to_string())),
2110 };
2111 file.set_modified(std::time::SystemTime::now())
2112 .map_err(|err| GitError::Io(err.to_string()))?;
2113 Ok(true)
2114}
2115
2116pub(crate) fn promisor_pack_object_ids(
2117 objects_dir: &Path,
2118 format: ObjectFormat,
2119) -> Result<HashSet<ObjectId>> {
2120 let pack_dir = objects_dir.join("pack");
2121 let mut oids = HashSet::new();
2122 if !pack_dir.exists() {
2123 return Ok(oids);
2124 }
2125 for entry in fs::read_dir(pack_dir)? {
2126 let path = entry?.path();
2127 if path.extension().and_then(|ext| ext.to_str()) != Some("idx") {
2128 continue;
2129 }
2130 if !path.with_extension("pack").exists() || !path.with_extension("promisor").exists() {
2131 continue;
2132 }
2133 let index = PackIndex::parse(&fs::read(path)?, format)?;
2134 oids.extend(index.entries.into_iter().map(|entry| entry.oid));
2135 }
2136 Ok(oids)
2137}
2138
2139pub(crate) fn promisor_object_links(format: ObjectFormat, object: &EncodedObject) -> Vec<ObjectId> {
2140 match object.object_type {
2141 ObjectType::Commit => Commit::parse_ref(format, &object.body)
2142 .map(|commit| {
2143 let mut links = Vec::with_capacity(commit.parents.len() + 1);
2144 links.push(commit.tree);
2145 links.extend(commit.parents);
2146 links
2147 })
2148 .unwrap_or_default(),
2149 ObjectType::Tree => TreeEntries::new(format, &object.body)
2150 .filter_map(|entry| entry.ok().map(|entry| entry.oid))
2151 .collect(),
2152 ObjectType::Tag => Tag::parse_ref(format, &object.body)
2153 .map(|tag| vec![tag.object])
2154 .unwrap_or_default(),
2155 ObjectType::Blob => Vec::new(),
2156 }
2157}
2158impl ObjectWriter for FileObjectDatabase {
2159 fn write_object(&self, object: EncodedObject) -> Result<ObjectId> {
2160 let oid = object.object_id(self.format)?;
2167 if self.freshen_existing_object(&oid)? {
2168 return Ok(oid);
2169 }
2170 self.loose.write_object(object)
2171 }
2172}