git_odb/store_impls/loose/
verify.rs

1use std::{
2    sync::atomic::{AtomicBool, Ordering},
3    time::Instant,
4};
5
6use git_features::progress::Progress;
7
8use crate::{loose::Store, Write};
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: git_object::decode::Error,
19            kind: git_object::Kind,
20            id: git_hash::ObjectId,
21        },
22        #[error("{kind} object {expected} wasn't re-encoded without change - new hash is {actual}")]
23        ObjectHashMismatch {
24            kind: git_object::Kind,
25            actual: git_hash::ObjectId,
26            expected: git_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 = "serde1", 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 git_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        mut progress: impl Progress,
65        should_interrupt: &AtomicBool,
66    ) -> Result<integrity::Statistics, integrity::Error> {
67        let mut buf = Vec::new();
68        let sink = crate::sink(self.object_hash);
69
70        let mut num_objects = 0;
71        let start = Instant::now();
72        let mut progress = progress.add_child_with_id("Validating", integrity::ProgressId::LooseObjects.into());
73        progress.init(None, git_features::progress::count("loose objects"));
74        for id in self.iter().filter_map(Result::ok) {
75            let object = self
76                .try_find(id, &mut buf)
77                .map_err(|_| integrity::Error::Retry)?
78                .ok_or(integrity::Error::Retry)?;
79            let actual_id = sink.write_buf(object.kind, object.data).expect("sink never fails");
80            if actual_id != id {
81                return Err(integrity::Error::ObjectHashMismatch {
82                    kind: object.kind,
83                    actual: actual_id,
84                    expected: id,
85                });
86            }
87            object.decode().map_err(|err| integrity::Error::ObjectDecode {
88                source: err,
89                kind: object.kind,
90                id,
91            })?;
92
93            progress.inc();
94            num_objects += 1;
95            if should_interrupt.load(Ordering::SeqCst) {
96                return Err(integrity::Error::Interrupted);
97            }
98        }
99        progress.show_throughput(start);
100
101        Ok(integrity::Statistics { num_objects })
102    }
103}