Skip to main content

grit_lib/odb/store/
files.rs

1//! On-disk object source for one `objects/` directory (MIDX, packs, loose).
2
3use 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
22/// Read/write object storage backed by one `objects/` directory tree.
23pub 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    /// Open a files source for `pack_store`'s directory.
44    ///
45    /// `use_midx` enables multi-pack-index reads when a chain is present.
46    /// `midx_tip_listing_for_exists` matches Git's MIDX membership probe for [`ObjectStore::contains`].
47    ///
48    /// # Errors
49    ///
50    /// Propagates [`MidxObjects::new`] failures other than a missing index.
51    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    /// `objects/` root for this source.
71    #[must_use]
72    pub fn objects_dir(&self) -> &Path {
73        &self.objects_dir
74    }
75
76    /// Loose-object backend (writes target this store).
77    #[must_use]
78    pub fn loose_store(&self) -> &LooseStore {
79        &self.loose
80    }
81
82    /// Shared pack read backend.
83    #[must_use]
84    pub fn packed_objects(&self) -> &PackedObjects {
85        &self.packs
86    }
87
88    /// MIDX layer for this directory.
89    #[must_use]
90    pub fn midx_objects(&self) -> &MidxObjects {
91        &self.midx
92    }
93
94    /// Whether `oid` is materialized under this `objects/` tree (local loose or non-promisor pack).
95    #[must_use]
96    pub fn exists_local_materialized(&self, oid: &ObjectId) -> bool {
97        super::local_object_materialized(&self.objects_dir, oid)
98    }
99
100    /// Resolve duplicate-write short-circuit for this directory before creating a loose file.
101    ///
102    /// Returns `Some(oid)` when the object is already present locally and was freshened when
103    /// appropriate. When `options.trust_new_loose` is set, only an existing loose file stops the
104    /// write (callers may still consult alternates).
105    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            // Cruft packs (and other unfreshenable local copies) must not block loose
121            // materialization — matches `try_freshen_existing_local` / unpack-objects.
122            return Ok(None);
123        }
124        Ok(None)
125    }
126
127    /// Write zlib-compressed store bytes for a precomputed `oid` into this source's loose store.
128    ///
129    /// # Errors
130    ///
131    /// Same as [`WritableObjectStore::write`].
132    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    /// Whether a loose object file exists but could not be parsed (caller may consult alternates).
145    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(_)
152                | Error::CorruptLooseObject { .. }
153                | Error::LooseHashMismatch { .. }
154                | Error::Zlib(_)
155        )
156    }
157
158    fn midx_pack_filter_active(&self) -> bool {
159        self.use_midx && self.midx.status() == MidxObjectsStatus::Active
160    }
161
162    fn midx_read_should_fallback_to_packs(err: &Error) -> bool {
163        err.is_damaged_object()
164    }
165
166    fn try_read_object_into(
167        &self,
168        oid: &ObjectId,
169        populate_delta_lru: bool,
170        buf: &mut Vec<u8>,
171    ) -> Result<Option<ObjectKind>> {
172        if empty_tree_oid_matches(self.hash_algo(), oid) {
173            buf.clear();
174            return Ok(Some(ObjectKind::Tree));
175        }
176
177        let midx_pack_filter = self.midx_pack_filter_active();
178        let pack_opts = PackLookupOptions {
179            skip_midx_covered_packs: midx_pack_filter,
180            only_midx_covered_packs: false,
181        };
182
183        if self.use_midx {
184            match self.midx.read_into(oid, buf) {
185                Ok(Some(kind)) => return Ok(Some(kind)),
186                Ok(None) => {}
187                Err(Error::Midx(_)) => {}
188                Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
189                Err(err) => return Err(err),
190            }
191        }
192
193        match pack::try_read_object_from_packs_with_options_into(
194            &self.objects_dir,
195            oid,
196            pack_opts,
197            populate_delta_lru,
198            buf,
199        ) {
200            Ok(kind) => return Ok(Some(kind)),
201            Err(Error::ObjectNotFound(_)) => {}
202            Err(err) => return Err(err),
203        }
204
205        if midx_pack_filter {
206            match pack::try_read_object_from_packs_with_options_into(
207                &self.objects_dir,
208                oid,
209                PackLookupOptions::MIDX_COVERED_PACKS,
210                populate_delta_lru,
211                buf,
212            ) {
213                Ok(kind) => return Ok(Some(kind)),
214                Err(Error::ObjectNotFound(_)) => {}
215                Err(err) => return Err(err),
216            }
217        }
218
219        #[cfg(test)]
220        crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
221        self.loose.read_into(oid, buf)
222    }
223
224    fn try_read_object(&self, oid: &ObjectId) -> Result<Option<Object>> {
225        if empty_tree_oid_matches(self.hash_algo(), oid) {
226            return Ok(Some(Object {
227                kind: ObjectKind::Tree,
228                data: Vec::new(),
229            }));
230        }
231
232        let midx_pack_filter = self.midx_pack_filter_active();
233        let pack_opts = PackLookupOptions {
234            skip_midx_covered_packs: midx_pack_filter,
235            only_midx_covered_packs: false,
236        };
237
238        if self.use_midx {
239            match self.midx.read(oid) {
240                Ok(Some(obj)) => return Ok(Some(obj)),
241                Ok(None) => {}
242                Err(Error::Midx(_)) => {}
243                Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
244                Err(err) => return Err(err),
245            }
246        }
247
248        match pack::try_read_object_from_packs_with_options(&self.objects_dir, oid, pack_opts) {
249            Ok(obj) => return Ok(Some(obj)),
250            Err(Error::ObjectNotFound(_)) => {}
251            Err(err) => return Err(err),
252        }
253
254        if midx_pack_filter {
255            match pack::try_read_object_from_packs_with_options(
256                &self.objects_dir,
257                oid,
258                PackLookupOptions::MIDX_COVERED_PACKS,
259            ) {
260                Ok(obj) => return Ok(Some(obj)),
261                Err(Error::ObjectNotFound(_)) => {}
262                Err(err) => return Err(err),
263            }
264        }
265
266        #[cfg(test)]
267        crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
268        self.loose.read(oid)
269    }
270
271    fn try_read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
272        if empty_tree_oid_matches(self.hash_algo(), oid) {
273            return Ok(Some(ObjectInfo {
274                kind: ObjectKind::Tree,
275                size: 0,
276            }));
277        }
278
279        let midx_pack_filter = self.midx_pack_filter_active();
280        let pack_opts = PackLookupOptions {
281            skip_midx_covered_packs: midx_pack_filter,
282            only_midx_covered_packs: false,
283        };
284
285        if self.use_midx {
286            match self.midx.read_info(oid) {
287                Ok(Some(info)) => return Ok(Some(info)),
288                Ok(None) => {}
289                Err(Error::Midx(_)) => {}
290                Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
291                Err(err) => return Err(err),
292            }
293        }
294
295        match pack::try_read_object_info_from_packs_with_options(&self.objects_dir, oid, pack_opts)
296        {
297            Ok(info) => return Ok(Some(info)),
298            Err(Error::ObjectNotFound(_)) => {}
299            Err(err) => return Err(err),
300        }
301
302        if midx_pack_filter {
303            match pack::try_read_object_info_from_packs_with_options(
304                &self.objects_dir,
305                oid,
306                PackLookupOptions::MIDX_COVERED_PACKS,
307            ) {
308                Ok(info) => return Ok(Some(info)),
309                Err(Error::ObjectNotFound(_)) => {}
310                Err(err) => return Err(err),
311            }
312        }
313
314        #[cfg(test)]
315        crate::hot_path_test_metrics::record_loose_path_open_for_active_scope();
316        self.loose.read_info(oid)
317    }
318
319    /// Find `oid` locally, reading it only when it is a loose object of at most `limit` bytes.
320    ///
321    /// Packed objects are not read here ([`LocalSizedRead::Packed`]); one stored whole is sized
322    /// from its entry header without inflating it. Loose objects are opened once, and inflation
323    /// stops at the header when the object is too large.
324    ///
325    /// # Errors
326    ///
327    /// Propagates pack lookup and loose read failures.
328    pub(crate) fn read_unless_larger(&self, oid: &ObjectId, limit: u64) -> Result<LocalSizedRead> {
329        let probe = || {
330            match pack::locate_packed_whole_object(&self.objects_dir, oid, false)? {
331                pack::PackedEntry::Whole(whole) => {
332                    return Ok(LocalSizedRead::Packed(Some(whole.size)))
333                }
334                pack::PackedEntry::NotWhole => return Ok(LocalSizedRead::Packed(None)),
335                pack::PackedEntry::Absent => {}
336            }
337            Ok(match self.loose.read_unless_larger(oid, limit)? {
338                Some(read) => LocalSizedRead::Loose(read),
339                None => LocalSizedRead::Missing,
340            })
341        };
342        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
343            probe()
344        } else {
345            PackStore::with_context(self.packs.pack_store().clone(), probe)
346        }
347    }
348
349    fn read_with_pack_retry(&self, oid: &ObjectId) -> Result<Option<Object>> {
350        match self.try_read_object(oid) {
351            Ok(Some(obj)) => Ok(Some(obj)),
352            Ok(None) => {
353                if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
354                    self.try_read_object(oid)
355                } else {
356                    Ok(None)
357                }
358            }
359            Err(err) => Err(err),
360        }
361    }
362
363    fn read_with_pack_retry_into(
364        &self,
365        oid: &ObjectId,
366        populate_delta_lru: bool,
367        buf: &mut Vec<u8>,
368    ) -> Result<Option<ObjectKind>> {
369        match self.try_read_object_into(oid, populate_delta_lru, buf) {
370            Ok(Some(kind)) => Ok(Some(kind)),
371            Ok(None) => {
372                if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
373                    self.try_read_object_into(oid, populate_delta_lru, buf)
374                } else {
375                    Ok(None)
376                }
377            }
378            Err(err) => Err(err),
379        }
380    }
381
382    fn read_info_with_pack_retry(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
383        match self.try_read_info(oid) {
384            Ok(Some(info)) => Ok(Some(info)),
385            Ok(None) => {
386                if pack::reprepare_pack_directory_on_miss(&self.objects_dir)? {
387                    self.try_read_info(oid)
388                } else {
389                    Ok(None)
390                }
391            }
392            Err(err) => Err(err),
393        }
394    }
395
396    fn contains_in_packs(&self, oid: &ObjectId) -> Result<bool> {
397        let indexes = pack::read_local_pack_indexes_cached(&self.objects_dir)?;
398        if indexes.is_empty() && !pack_directory_has_index_files(&self.objects_dir) {
399            if self.midx_tip_listing_for_exists && self.use_midx {
400                match midx_oid_listed_in_tip(&self.objects_dir, oid) {
401                    Ok(Some(true)) => return Ok(true),
402                    Ok(Some(false)) | Ok(None) => {}
403                    Err(_) => return Ok(false),
404                }
405            }
406            return Ok(false);
407        }
408        let filter = super::PackFilter {
409            include_promisor: true,
410            include_cruft: true,
411        };
412        if self.packs.contains_filtered(oid, filter)? {
413            return Ok(true);
414        }
415        if pack::reprepare_pack_directory_on_miss(&self.objects_dir)?
416            && self.packs.contains_filtered(oid, filter)?
417        {
418            return Ok(true);
419        }
420        if self.midx_tip_listing_for_exists && self.use_midx {
421            match midx_oid_listed_in_tip(&self.objects_dir, oid) {
422                Ok(Some(true)) => return Ok(true),
423                Ok(Some(false)) | Ok(None) => {}
424                Err(_) => return Ok(false),
425            }
426        }
427        Ok(false)
428    }
429}
430
431impl FilesSource {
432    /// [`ObjectStore::read_into`], keeping delta bases resolved from packs in the delta-base
433    /// cache when `populate_delta_lru` is set.
434    pub(crate) fn read_into_with_delta_policy(
435        &self,
436        oid: &ObjectId,
437        populate_delta_lru: bool,
438        buf: &mut Vec<u8>,
439    ) -> Result<Option<ObjectKind>> {
440        let mut read = || self.read_with_pack_retry_into(oid, populate_delta_lru, buf);
441        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
442            read()
443        } else {
444            PackStore::with_context(self.packs.pack_store().clone(), read)
445        }
446    }
447
448    /// See [`crate::odb::Odb::read_packed_commit_header_block_into`].
449    pub(crate) fn read_packed_commit_header_block_into(
450        &self,
451        oid: &ObjectId,
452        buf: &mut Vec<u8>,
453    ) -> Result<bool> {
454        let mut read = || pack::read_packed_commit_header_block_into(&self.objects_dir, oid, buf);
455        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
456            read()
457        } else {
458            PackStore::with_context(self.packs.pack_store().clone(), read)
459        }
460    }
461}
462
463impl ObjectStore for FilesSource {
464    fn hash_algo(&self) -> HashAlgo {
465        ObjectStore::hash_algo(&self.loose)
466    }
467
468    fn read(&self, oid: &ObjectId) -> Result<Option<Object>> {
469        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
470            return self.read_with_pack_retry(oid);
471        }
472        PackStore::with_context(self.packs.pack_store().clone(), || {
473            self.read_with_pack_retry(oid)
474        })
475    }
476
477    fn read_into(&self, oid: &ObjectId, buf: &mut Vec<u8>) -> Result<Option<ObjectKind>> {
478        self.read_into_with_delta_policy(oid, false, buf)
479    }
480
481    fn read_info(&self, oid: &ObjectId) -> Result<Option<ObjectInfo>> {
482        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
483            return self.read_info_with_pack_retry(oid);
484        }
485        PackStore::with_context(self.packs.pack_store().clone(), || {
486            self.read_info_with_pack_retry(oid)
487        })
488    }
489
490    fn contains(&self, oid: &ObjectId) -> Result<bool> {
491        if empty_tree_oid_matches(self.hash_algo(), oid) {
492            return Ok(true);
493        }
494        if self.loose.object_path(oid).is_file() {
495            return Ok(true);
496        }
497        let check = || self.contains_in_packs(oid);
498        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
499            check()
500        } else {
501            PackStore::with_context(self.packs.pack_store().clone(), check)
502        }
503    }
504
505    fn contains_many(&self, oids: &[ObjectId]) -> Result<Vec<bool>> {
506        // Batches are mostly packed objects (cached trees, unchanged tree members), so
507        // the loaded pack indexes answer first and only misses pay a loose-path stat. A
508        // pack failure falls through to `contains`, which reports it as a single probe would.
509        let filter = super::PackFilter {
510            include_promisor: true,
511            include_cruft: true,
512        };
513        let probe = || {
514            oids.iter()
515                .map(|oid| {
516                    if self.packs.contains_filtered(oid, filter).unwrap_or(false) {
517                        return Ok(true);
518                    }
519                    self.contains(oid)
520                })
521                .collect()
522        };
523        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
524            probe()
525        } else {
526            PackStore::with_context(self.packs.pack_store().clone(), probe)
527        }
528    }
529
530    fn open_stream(&self, oid: &ObjectId) -> Result<Option<ObjectStream<'_>>> {
531        if empty_tree_oid_matches(self.hash_algo(), oid) {
532            return Ok(Some(ObjectStream {
533                kind: ObjectKind::Tree,
534                size: 0,
535                reader: Box::new(std::io::Cursor::new(Vec::<u8>::new())),
536            }));
537        }
538        PackStore::with_context(self.packs.pack_store().clone(), || {
539            if self.use_midx {
540                match self.midx.open_stream(oid) {
541                    Ok(Some(stream)) => return Ok(Some(stream)),
542                    Ok(None) => {}
543                    Err(Error::Midx(_)) => {}
544                    Err(err) if Self::midx_read_should_fallback_to_packs(&err) => {}
545                    Err(err) => return Err(err),
546                }
547            }
548            match self.packs.open_stream(oid) {
549                Ok(Some(stream)) => return Ok(Some(stream)),
550                Ok(None) | Err(Error::ObjectNotFound(_)) => {}
551                Err(err) => return Err(err),
552            }
553            self.loose.open_stream(oid)
554        })
555    }
556
557    fn for_each_object(&self, f: &mut dyn FnMut(&ObjectId) -> ControlFlow<()>) -> Result<()> {
558        let mut seen = HashSet::new();
559        PackStore::with_context(self.packs.pack_store().clone(), || {
560            let mut layer = |store: &dyn ObjectStore| -> Result<bool> {
561                super::for_each_propagate_break(store, &mut |oid| {
562                    if !seen.insert(*oid) {
563                        return ControlFlow::Continue(());
564                    }
565                    f(oid)
566                })
567            };
568            if self.use_midx && layer(&self.midx)? {
569                return Ok(());
570            }
571            if layer(&self.packs)? {
572                return Ok(());
573            }
574            let _ = layer(&self.loose)?;
575            Ok(())
576        })
577    }
578
579    fn lookup_prefix(&self, prefix: &str, limit: usize, out: &mut Vec<ObjectId>) -> Result<()> {
580        ObjectIdPrefix::from_hex(prefix, self.hash_algo())?;
581        let mut seen: HashSet<ObjectId> = out.iter().copied().collect();
582        if PackStore::read_context_matches_objects_dir(&self.objects_dir) {
583            files_lookup_prefix_layers(self, prefix, limit, out, &mut seen)
584        } else {
585            PackStore::with_context(self.packs.pack_store().clone(), || {
586                files_lookup_prefix_layers(self, prefix, limit, out, &mut seen)
587            })
588        }
589    }
590
591    fn refresh(&self) -> Result<bool> {
592        PackStore::with_context(self.packs.pack_store().clone(), || {
593            let mut changed = self.packs.refresh()?;
594            if self.midx.refresh()? {
595                changed = true;
596            }
597            Ok(changed)
598        })
599    }
600}
601
602impl WritableObjectStore for FilesSource {
603    fn write(&self, kind: ObjectKind, data: &[u8], options: WriteOptions) -> Result<ObjectId> {
604        let oid = hash::hash_object(self.hash_algo(), kind, data)?;
605        if let Some(existing) = self.try_finish_duplicate_write(&oid, options)? {
606            return Ok(existing);
607        }
608        self.loose.write(kind, data, options)
609    }
610
611    fn freshen(&self, oid: &ObjectId) -> Result<bool> {
612        if self.loose.freshen(oid)? {
613            return Ok(true);
614        }
615        PackStore::with_context(self.packs.pack_store().clone(), || {
616            self.packs.freshen_pack_containing(oid)
617        })
618    }
619}
620
621fn pack_directory_has_index_files(objects_dir: &Path) -> bool {
622    let pack_dir = objects_dir.join("pack");
623    let Ok(entries) = std::fs::read_dir(pack_dir) else {
624        return false;
625    };
626    entries
627        .filter_map(|e| e.ok())
628        .any(|ent| ent.path().extension().is_some_and(|ext| ext == "idx"))
629}
630
631fn files_lookup_prefix_layers(
632    src: &FilesSource,
633    prefix: &str,
634    limit: usize,
635    out: &mut Vec<ObjectId>,
636    seen: &mut HashSet<ObjectId>,
637) -> Result<()> {
638    if src.use_midx {
639        merge_prefix_matches(&src.midx, prefix, limit, out, seen)?;
640        if limit != 0 && out.len() >= limit {
641            return Ok(());
642        }
643    }
644    merge_prefix_matches(&src.packs, prefix, limit, out, seen)?;
645    if limit != 0 && out.len() >= limit {
646        return Ok(());
647    }
648    merge_prefix_matches(&src.loose, prefix, limit, out, seen)
649}
650
651fn merge_prefix_matches(
652    store: &dyn ObjectStore,
653    prefix: &str,
654    limit: usize,
655    out: &mut Vec<ObjectId>,
656    seen: &mut HashSet<ObjectId>,
657) -> Result<()> {
658    if limit != 0 && out.len() >= limit {
659        return Ok(());
660    }
661    if limit == 0 {
662        let mut scratch = Vec::new();
663        store.lookup_prefix(prefix, 0, &mut scratch)?;
664        for oid in scratch {
665            if seen.insert(oid) {
666                out.push(oid);
667            }
668        }
669        return Ok(());
670    }
671
672    let goal = limit;
673    let mut fetch = goal.saturating_sub(out.len());
674    loop {
675        let mut scratch = Vec::new();
676        store.lookup_prefix(prefix, fetch, &mut scratch)?;
677        let scratch_len = scratch.len();
678        let before = out.len();
679        for oid in scratch {
680            if seen.insert(oid) {
681                out.push(oid);
682                if out.len() >= goal {
683                    return Ok(());
684                }
685            }
686        }
687        if out.len() >= goal {
688            return Ok(());
689        }
690        if scratch_len < fetch {
691            return Ok(());
692        }
693        let need = goal.saturating_sub(out.len());
694        if need == 0 {
695            return Ok(());
696        }
697        fetch = if before == out.len() {
698            fetch.saturating_add(need)
699        } else {
700            scratch_len.saturating_add(need)
701        };
702    }
703}