1use std::collections::HashSet;
4use std::ops::ControlFlow;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use crate::diagnostics::DiagnosticsHandle;
9use crate::error::{Error, Result};
10use crate::hash;
11use crate::midx::midx_oid_listed_in_tip;
12use crate::objects::{HashAlgo, Object, ObjectId, ObjectIdPrefix, ObjectInfo, ObjectKind};
13use crate::pack::{self, PackLookupOptions};
14use crate::pack_store::PackStore;
15
16use super::{
17 LocalSizedRead, LooseStore, MidxObjects, MidxObjectsStatus, ObjectStore, ObjectStream,
18 PackedObjects, WritableObjectStore,
19};
20use crate::odb::{empty_tree_oid_matches, WriteOptions};
21
22pub struct FilesSource {
24 objects_dir: PathBuf,
25 loose: LooseStore,
26 packs: PackedObjects,
27 midx: MidxObjects,
28 use_midx: bool,
29 midx_tip_listing_for_exists: bool,
30 _diagnostics: DiagnosticsHandle,
31}
32
33impl std::fmt::Debug for FilesSource {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("FilesSource")
36 .field("objects_dir", &self.objects_dir)
37 .field("use_midx", &self.use_midx)
38 .finish()
39 }
40}
41
42impl FilesSource {
43 fn with_pack_access<R>(&self, f: impl FnOnce() -> R) -> R {
44 if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
45 f()
46 } else if PackStore::has_context() {
47 pack::with_alternate_pack_store(self.packs.pack_store().clone(), f)
48 } else {
49 PackStore::with_context(self.packs.pack_store().clone(), f)
50 }
51 }
52
53 fn skip_loose_probe_after_empty_warmup(&self, oid: &ObjectId) -> bool {
54 pack::pack_cache::trusted_verified_empty_pack_listing(&self.objects_dir)
55 && self.loose.cached_absent_loose_path(oid)
56 }
57
58 pub fn open(
67 pack_store: Arc<PackStore>,
68 loose: LooseStore,
69 use_midx: bool,
70 midx_tip_listing_for_exists: bool,
71 diagnostics: DiagnosticsHandle,
72 ) -> Result<Self> {
73 let midx = MidxObjects::new(Arc::clone(&pack_store), use_midx)?;
74 Ok(Self {
75 objects_dir: pack_store.objects_dir().to_path_buf(),
76 loose,
77 packs: PackedObjects::new(pack_store),
78 midx,
79 use_midx,
80 midx_tip_listing_for_exists,
81 _diagnostics: diagnostics,
82 })
83 }
84
85 #[must_use]
87 pub fn objects_dir(&self) -> &Path {
88 &self.objects_dir
89 }
90
91 #[must_use]
93 pub fn loose_store(&self) -> &LooseStore {
94 &self.loose
95 }
96
97 #[must_use]
99 pub fn packed_objects(&self) -> &PackedObjects {
100 &self.packs
101 }
102
103 #[must_use]
105 pub fn exists_local_materialized(&self, oid: &ObjectId) -> bool {
106 super::local_object_materialized(&self.objects_dir, oid)
107 }
108
109 pub fn try_finish_duplicate_write(
115 &self,
116 oid: &ObjectId,
117 options: WriteOptions,
118 ) -> Result<Option<ObjectId>> {
119 if let Some(existing) = self.loose.try_finish_if_loose_present(oid, options)? {
120 return Ok(Some(existing));
121 }
122 if options.trust_new_loose {
123 return Ok(None);
124 }
125 if options.assume_loose_only_existence && self.exists_local_materialized(oid) {
126 if self.freshen(oid)? {
127 return Ok(Some(*oid));
128 }
129 return Ok(None);
132 }
133 Ok(None)
134 }
135
136 pub fn write_zlib_prehashed(
142 &self,
143 oid: &ObjectId,
144 zlib_store: &[u8],
145 options: WriteOptions,
146 ) -> Result<ObjectId> {
147 if let Some(existing) = self.try_finish_duplicate_write(oid, options)? {
148 return Ok(existing);
149 }
150 self.loose.write_zlib_prehashed(oid, zlib_store, options)
151 }
152
153 pub fn local_loose_exists_but_unreadable(&self, oid: &ObjectId, err: &Error) -> bool {
155 if !self.loose.object_path(oid).is_file() {
156 return false;
157 }
158 matches!(
159 err,
160 Error::CorruptObject(_)
161 | Error::CorruptLooseObject { .. }
162 | Error::LooseHashMismatch { .. }
163 | Error::Zlib(_)
164 )
165 }
166
167 fn midx_pack_filter_active(&self) -> bool {
168 self.use_midx && self.midx.status() == MidxObjectsStatus::Active
169 }
170
171 pub(crate) fn midx_reads_active(&self) -> bool {
173 self.midx_pack_filter_active()
174 }
175
176 fn midx_read_should_fallback_to_packs(err: &Error) -> bool {
177 err.is_damaged_object()
178 }
179
180 fn try_read_object_into(
181 &self,
182 oid: &ObjectId,
183 populate_delta_lru: bool,
184 buf: &mut Vec<u8>,
185 ) -> Result<Option<ObjectKind>> {
186 if empty_tree_oid_matches(self.hash_algo(), oid) {
187 buf.clear();
188 return Ok(Some(ObjectKind::Tree));
189 }
190
191 let midx_pack_filter = self.midx_pack_filter_active();
192 let pack_opts = PackLookupOptions {
193 skip_midx_covered_packs: midx_pack_filter,
194 only_midx_covered_packs: false,
195 };
196
197 if self.use_midx {
198 match self.midx.read_into(oid, buf) {
199 Ok(Some(kind)) => return Ok(Some(kind)),
200 Ok(None) => {}
201 Err(Error::Midx(_)) => {}
202 Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
203 Err(err) => return Err(err),
204 }
205 }
206
207 match pack::try_read_object_from_packs_with_options_into(
208 &self.objects_dir,
209 oid,
210 pack_opts,
211 populate_delta_lru,
212 buf,
213 ) {
214 Ok(kind) => return Ok(Some(kind)),
215 Err(Error::ObjectNotFound(_)) => {}
216 Err(err) => return Err(err),
217 }
218
219 if midx_pack_filter {
220 match pack::try_read_object_from_packs_with_options_into(
221 &self.objects_dir,
222 oid,
223 PackLookupOptions::MIDX_COVERED_PACKS,
224 populate_delta_lru,
225 buf,
226 ) {
227 Ok(kind) => return Ok(Some(kind)),
228 Err(Error::ObjectNotFound(_)) => {}
229 Err(err) => return Err(err),
230 }
231 }
232
233 if self.skip_loose_probe_after_empty_warmup(oid) {
234 return Ok(None);
235 }
236 #[cfg(test)]
237 crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
238 self.loose.read_into(oid, buf)
239 }
240
241 fn try_read_object(&self, oid: &ObjectId) -> Result<Option<Object>> {
242 if empty_tree_oid_matches(self.hash_algo(), oid) {
243 return Ok(Some(Object {
244 kind: ObjectKind::Tree,
245 data: Vec::new(),
246 }));
247 }
248
249 let midx_pack_filter = self.midx_pack_filter_active();
250 let pack_opts = PackLookupOptions {
251 skip_midx_covered_packs: midx_pack_filter,
252 only_midx_covered_packs: false,
253 };
254
255 if self.use_midx {
256 match self.midx.read(oid) {
257 Ok(Some(obj)) => return Ok(Some(obj)),
258 Ok(None) => {}
259 Err(Error::Midx(_)) => {}
260 Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
261 Err(err) => return Err(err),
262 }
263 }
264
265 match pack::try_read_object_from_packs_with_options(&self.objects_dir, oid, pack_opts) {
266 Ok(obj) => return Ok(Some(obj)),
267 Err(Error::ObjectNotFound(_)) => {}
268 Err(err) => return Err(err),
269 }
270
271 if midx_pack_filter {
272 match pack::try_read_object_from_packs_with_options(
273 &self.objects_dir,
274 oid,
275 PackLookupOptions::MIDX_COVERED_PACKS,
276 ) {
277 Ok(obj) => return Ok(Some(obj)),
278 Err(Error::ObjectNotFound(_)) => {}
279 Err(err) => return Err(err),
280 }
281 }
282
283 if self.skip_loose_probe_after_empty_warmup(oid) {
284 return Ok(None);
285 }
286 #[cfg(test)]
287 crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
288 self.loose.read(oid)
289 }
290
291 fn try_read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
292 if empty_tree_oid_matches(self.hash_algo(), oid) {
293 return Ok(Some(ObjectInfo {
294 kind: ObjectKind::Tree,
295 size: 0,
296 }));
297 }
298
299 let midx_pack_filter = self.midx_pack_filter_active();
300 let pack_opts = PackLookupOptions {
301 skip_midx_covered_packs: midx_pack_filter,
302 only_midx_covered_packs: false,
303 };
304
305 if self.use_midx {
306 match self.midx.read_info(oid) {
307 Ok(Some(info)) => return Ok(Some(info)),
308 Ok(None) => {}
309 Err(Error::Midx(_)) => {}
310 Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
311 Err(err) => return Err(err),
312 }
313 }
314
315 match pack::try_read_object_info_from_packs_with_options(&self.objects_dir, oid, pack_opts)
316 {
317 Ok(info) => return Ok(Some(info)),
318 Err(Error::ObjectNotFound(_)) => {}
319 Err(err) => return Err(err),
320 }
321
322 if midx_pack_filter {
323 match pack::try_read_object_info_from_packs_with_options(
324 &self.objects_dir,
325 oid,
326 PackLookupOptions::MIDX_COVERED_PACKS,
327 ) {
328 Ok(info) => return Ok(Some(info)),
329 Err(Error::ObjectNotFound(_)) => {}
330 Err(err) => return Err(err),
331 }
332 }
333
334 if self.skip_loose_probe_after_empty_warmup(oid) {
335 return Ok(None);
336 }
337 #[cfg(test)]
338 crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
339 self.loose.read_info(oid)
340 }
341
342 pub(crate) fn read_unless_larger(&self, oid: &ObjectId, limit: u64) -> Result<LocalSizedRead> {
352 let probe = || {
353 match pack::locate_packed_whole_object(&self.objects_dir, oid, false)? {
354 pack::PackedEntry::Whole(whole) => {
355 return Ok(LocalSizedRead::Packed(Some(whole.size)))
356 }
357 pack::PackedEntry::NotWhole => return Ok(LocalSizedRead::Packed(None)),
358 pack::PackedEntry::Absent => {}
359 }
360 Ok(match self.loose.read_unless_larger(oid, limit)? {
361 Some(read) => LocalSizedRead::Loose(read),
362 None => LocalSizedRead::Missing,
363 })
364 };
365 self.with_pack_access(probe)
366 }
367
368 fn read_with_pack_retry(&self, oid: &ObjectId) -> Result<Option<Object>> {
369 match self.try_read_object(oid) {
370 Ok(Some(obj)) => Ok(Some(obj)),
371 Ok(None) => {
372 if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
373 self.try_read_object(oid)
374 } else {
375 Ok(None)
376 }
377 }
378 Err(err) => Err(err),
379 }
380 }
381
382 fn read_with_pack_retry_into(
383 &self,
384 oid: &ObjectId,
385 populate_delta_lru: bool,
386 buf: &mut Vec<u8>,
387 ) -> Result<Option<ObjectKind>> {
388 match self.try_read_object_into(oid, populate_delta_lru, buf) {
389 Ok(Some(kind)) => Ok(Some(kind)),
390 Ok(None) => {
391 if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
392 self.try_read_object_into(oid, populate_delta_lru, buf)
393 } else {
394 Ok(None)
395 }
396 }
397 Err(err) => Err(err),
398 }
399 }
400
401 fn read_info_with_pack_retry(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
402 match self.try_read_info(oid) {
403 Ok(Some(info)) => Ok(Some(info)),
404 Ok(None) => {
405 if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
406 self.try_read_info(oid)
407 } else {
408 Ok(None)
409 }
410 }
411 Err(err) => Err(err),
412 }
413 }
414
415 fn contains_in_packs(&self, oid: &ObjectId) -> Result<bool> {
416 let indexes = pack::read_local_pack_indexes_cached(&self.objects_dir)?;
417 if indexes.is_empty() && !pack_directory_has_index_files(&self.objects_dir) {
418 if self.midx_tip_listing_for_exists && self.use_midx {
419 match midx_oid_listed_in_tip(&self.objects_dir, oid) {
420 Ok(Some(true)) => return Ok(true),
421 Ok(Some(false)) | Ok(None) => {}
422 Err(_) => return Ok(false),
423 }
424 }
425 return Ok(false);
426 }
427 let filter = super::PackFilter {
428 include_promisor: true,
429 include_cruft: true,
430 };
431 if self.packs.contains_filtered(oid, filter)? {
432 return Ok(true);
433 }
434 if pack::reprepare_pack_directory_on_miss(&self.objects_dir)?
435 && self.packs.contains_filtered(oid, filter)?
436 {
437 return Ok(true);
438 }
439 if self.midx_tip_listing_for_exists && self.use_midx {
440 match midx_oid_listed_in_tip(&self.objects_dir, oid) {
441 Ok(Some(true)) => return Ok(true),
442 Ok(Some(false)) | Ok(None) => {}
443 Err(_) => return Ok(false),
444 }
445 }
446 Ok(false)
447 }
448}
449
450impl FilesSource {
451 pub(crate) fn read_into_with_delta_policy(
454 &self,
455 oid: &ObjectId,
456 populate_delta_lru: bool,
457 buf: &mut Vec<u8>,
458 ) -> Result<Option<ObjectKind>> {
459 self.with_pack_access(|| self.read_with_pack_retry_into(oid, populate_delta_lru, buf))
460 }
461
462 pub(crate) fn read_packed_commit_header_block_into(
464 &self,
465 oid: &ObjectId,
466 buf: &mut Vec<u8>,
467 ) -> Result<bool> {
468 self.with_pack_access(|| {
469 pack::read_packed_commit_header_block_into(&self.objects_dir, oid, buf)
470 })
471 }
472
473 pub(crate) fn read_packed_commit_header_blocks(
475 &self,
476 oids: &[ObjectId],
477 found: impl FnMut(usize, &[u8]),
478 ) -> Result<()> {
479 self.with_pack_access(|| {
480 pack::read_packed_commit_header_blocks(&self.objects_dir, oids, found)
481 })
482 }
483}
484
485impl ObjectStore for FilesSource {
486 fn hash_algo(&self) -> HashAlgo {
487 ObjectStore::hash_algo(&self.loose)
488 }
489
490 fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
491 self.with_pack_access(|| self.read_with_pack_retry(oid))
492 }
493
494 fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
495 self.read_into_with_delta_policy(oid, false, buf)
496 }
497
498 fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
499 self.with_pack_access(|| self.read_info_with_pack_retry(oid))
500 }
501
502 fn contains(&self, oid: &ObjectId) -> Result<bool> {
503 if empty_tree_oid_matches(self.hash_algo(), oid) {
504 return Ok(true);
505 }
506 if self.loose.object_path(oid).is_file() {
507 return Ok(true);
508 }
509 self.with_pack_access(|| self.contains_in_packs(oid))
510 }
511
512 fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
513 let filter = super::PackFilter {
517 include_promisor: true,
518 include_cruft: true,
519 };
520 let probe = || {
521 oids.iter()
522 .map(|oid| {
523 if self.packs.contains_filtered(oid, filter).unwrap_or(false) {
524 return Ok(true);
525 }
526 self.contains(oid)
527 })
528 .collect()
529 };
530 self.with_pack_access(probe)
531 }
532
533 fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
534 if empty_tree_oid_matches(self.hash_algo(), oid) {
535 return Ok(Some(ObjectStream {
536 kind: ObjectKind::Tree,
537 size: 0,
538 reader: Box::new(std::io::Cursor::new(Vec::<u8>::new())),
539 }));
540 }
541 self.with_pack_access(|| {
542 if self.use_midx {
543 match self.midx.open_stream(oid) {
544 Ok(Some(stream)) => return Ok(Some(stream)),
545 Ok(None) => {}
546 Err(Error::Midx(_)) => {}
547 Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
548 Err(err) => return Err(err),
549 }
550 }
551 match self.packs.open_stream(oid) {
552 Ok(Some(stream)) => return Ok(Some(stream)),
553 Ok(None) | Err(Error::ObjectNotFound(_)) => {}
554 Err(err) => return Err(err),
555 }
556 self.loose.open_stream(oid)
557 })
558 }
559
560 fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
561 let mut seen = HashSet::new();
562 self.with_pack_access(|| {
563 let mut layer = |store: &dyn ObjectStore| -> Result<bool> {
564 super::for_each_propagate_break(store, &mut |oid| {
565 if !seen.insert(*oid) {
566 return ControlFlow::Continue(());
567 }
568 f(oid)
569 })
570 };
571 if self.use_midx && layer(&self.midx)? {
572 return Ok(());
573 }
574 if layer(&self.packs)? {
575 return Ok(());
576 }
577 let _ = layer(&self.loose)?;
578 Ok(())
579 })
580 }
581
582 fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
583 ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
584 let mut seen: HashSet<ObjectId> = out.iter().copied().collect();
585 self.with_pack_access(|| files_lookup_prefix_layers(self, prefix, limit, out, &mut seen))
586 }
587
588 fn refresh(&self) -> Result<bool> {
589 self.with_pack_access(|| {
590 let mut changed = self.packs.refresh()?;
591 if self.midx.refresh()? {
592 changed = true;
593 }
594 Ok(changed)
595 })
596 }
597}
598
599impl WritableObjectStore for FilesSource {
600 fn write(&self, kind: ObjectKind, data: &[u8], options: WriteOptions) -> Result<ObjectId> {
601 let oid = hash::hash_object(self.hash_algo(), kind, data)?;
602 if let Some(existing) = self.try_finish_duplicate_write(&oid, options)? {
603 return Ok(existing);
604 }
605 self.loose.write(kind, data, options)
606 }
607
608 fn freshen(&self, oid: &ObjectId) -> Result<bool> {
609 if self.loose.freshen(oid)? {
610 return Ok(true);
611 }
612 self.with_pack_access(|| self.packs.freshen_pack_containing(oid))
613 }
614}
615
616fn pack_directory_has_index_files(objects_dir: &Path) -> bool {
617 let pack_dir = objects_dir.join("pack");
618 let Ok(entries) = std::fs::read_dir(pack_dir) else {
619 return false;
620 };
621 entries
622 .filter_map(|e| e.ok())
623 .any(|ent| ent.path().extension().is_some_and(|ext| ext == "idx"))
624}
625
626fn files_lookup_prefix_layers(
627 src: &FilesSource,
628 prefix: &str,
629 limit: usize,
630 out: &mut Vec<ObjectId>,
631 seen: &mut HashSet<ObjectId>,
632) -> Result<()> {
633 if src.use_midx {
634 merge_prefix_matches(&src.midx, prefix, limit, out, seen)?;
635 if limit != 0 && out.len() >= limit {
636 return Ok(());
637 }
638 }
639 merge_prefix_matches(&src.packs, prefix, limit, out, seen)?;
640 if limit != 0 && out.len() >= limit {
641 return Ok(());
642 }
643 merge_prefix_matches(&src.loose, prefix, limit, out, seen)
644}
645
646fn merge_prefix_matches(
647 store: &dyn ObjectStore,
648 prefix: &str,
649 limit: usize,
650 out: &mut Vec<ObjectId>,
651 seen: &mut HashSet<ObjectId>,
652) -> Result<()> {
653 if limit != 0 && out.len() >= limit {
654 return Ok(());
655 }
656 if limit == 0 {
657 let mut scratch = Vec::new();
658 store.lookup_prefix(prefix, 0, &mut scratch)?;
659 for oid in scratch {
660 if seen.insert(oid) {
661 out.push(oid);
662 }
663 }
664 return Ok(());
665 }
666
667 let goal = limit;
668 let mut fetch = goal.saturating_sub(out.len());
669 loop {
670 let mut scratch = Vec::new();
671 store.lookup_prefix(prefix, fetch, &mut scratch)?;
672 let scratch_len = scratch.len();
673 let before = out.len();
674 for oid in scratch {
675 if seen.insert(oid) {
676 out.push(oid);
677 if out.len() >= goal {
678 return Ok(());
679 }
680 }
681 }
682 if out.len() >= goal {
683 return Ok(());
684 }
685 if scratch_len < fetch {
686 return Ok(());
687 }
688 let need = goal.saturating_sub(out.len());
689 if need == 0 {
690 return Ok(());
691 }
692 fetch = if before == out.len() {
693 fetch.saturating_add(need)
694 } else {
695 scratch_len.saturating_add(need)
696 };
697 }
698}