gix_odb/store_impls/loose/
verify.rs1use std::{
2 sync::atomic::{AtomicBool, Ordering},
3 time::Instant,
4};
5
6use gix_features::progress::{Count, DynNestedProgress, Progress};
7
8use crate::loose::Store;
9
10pub mod integrity {
12 #[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 #[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone)]
36 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37 pub struct Statistics {
38 pub num_objects: usize,
40 }
41
42 #[derive(Debug, Copy, Clone)]
46 pub enum ProgressId {
47 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 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}