gix_odb/store_impls/loose/
verify.rs

1use std::{
2    sync::atomic::{AtomicBool, Ordering},
3    time::Instant,
4};
5
6use gix_features::progress::{Count, DynNestedProgress, Progress};
7
8use crate::loose::Store;
9
10///
11pub mod integrity {
12    /// The error returned by [`verify_integrity()`][super::Store::verify_integrity()].
13    #[derive(Debug, thiserror::Error)]
14    #[allow(missing_docs)]
15    pub enum Error {
16        #[error("{kind} object {id} could not be decoded")]
17        ObjectDecode {
18            source: gix_object::decode::Error,
19            kind: gix_object::Kind,
20            id: gix_hash::ObjectId,
21        },
22        #[error("{kind} object {expected} could not be hashed")]
23        ObjectHasher {
24            #[source]
25            source: gix_hash::hasher::Error,
26            kind: gix_object::Kind,
27            expected: gix_hash::ObjectId,
28        },
29        #[error("{kind} object wasn't re-encoded without change")]
30        ObjectEncodeMismatch {
31            #[source]
32            source: gix_hash::verify::Error,
33            kind: gix_object::Kind,
34        },
35        #[error("Objects were deleted during iteration - try again")]
36        Retry,
37        #[error("Interrupted")]
38        Interrupted,
39    }
40
41    /// The outcome returned by [`verify_integrity()`][super::Store::verify_integrity()].
42    #[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone)]
43    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44    pub struct Statistics {
45        /// The amount of loose objects we checked.
46        pub num_objects: usize,
47    }
48
49    /// The progress ids used in [`verify_integrity()`][super::Store::verify_integrity()].
50    ///
51    /// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
52    #[derive(Debug, Copy, Clone)]
53    pub enum ProgressId {
54        /// The amount of loose objects that have been verified.
55        LooseObjects,
56    }
57
58    impl From<ProgressId> for gix_features::progress::Id {
59        fn from(v: ProgressId) -> Self {
60            match v {
61                ProgressId::LooseObjects => *b"VILO",
62            }
63        }
64    }
65}
66
67impl Store {
68    /// Check all loose objects for their integrity checking their hash matches the actual data and by decoding them fully.
69    pub fn verify_integrity(
70        &self,
71        progress: &mut dyn DynNestedProgress,
72        should_interrupt: &AtomicBool,
73    ) -> Result<integrity::Statistics, integrity::Error> {
74        use gix_object::Write;
75        let mut buf = Vec::new();
76        let sink = crate::sink(self.object_hash);
77
78        let mut num_objects = 0;
79        let start = Instant::now();
80        let mut progress = progress.add_child_with_id("Validating".into(), integrity::ProgressId::LooseObjects.into());
81        progress.init(None, gix_features::progress::count("loose objects"));
82        for id in self.iter().filter_map(Result::ok) {
83            let object = self
84                .try_find(&id, &mut buf)
85                .map_err(|_| integrity::Error::Retry)?
86                .ok_or(integrity::Error::Retry)?;
87            sink.write_buf(object.kind, object.data)
88                .map_err(|err| integrity::Error::ObjectHasher {
89                    source: *err.downcast().expect("sink can only fail in hasher"),
90                    kind: object.kind,
91                    expected: id,
92                })?
93                .verify(&id)
94                .map_err(|err| integrity::Error::ObjectEncodeMismatch {
95                    source: err,
96                    kind: object.kind,
97                })?;
98            object.decode().map_err(|err| integrity::Error::ObjectDecode {
99                source: err,
100                kind: object.kind,
101                id,
102            })?;
103
104            progress.inc();
105            num_objects += 1;
106            if should_interrupt.load(Ordering::SeqCst) {
107                return Err(integrity::Error::Interrupted);
108            }
109        }
110        progress.show_throughput(start);
111
112        Ok(integrity::Statistics { num_objects })
113    }
114}