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