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