Skip to main content

verbs/fsck/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Repository integrity checks.
3
4mod git_projection;
5mod objects;
6mod provenance;
7mod refs;
8mod state;
9#[cfg(test)]
10mod tests;
11
12use ::objects::{HeddleError, error::Result};
13use schemars::JsonSchema;
14use serde::Serialize;
15
16use crate::{ExecutionContext, HeddleReport, MachineOutputKind, ReportContract, schema_for_report};
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
19pub struct FsckOptions {
20    pub full: bool,
21    pub thorough: bool,
22    pub provenance: bool,
23    pub git_projection: bool,
24}
25
26#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
27pub struct FsckReport {
28    pub valid: bool,
29    pub errors: Vec<FsckError>,
30    pub warnings: Vec<String>,
31    pub objects_checked: usize,
32    pub git_projection_checked: bool,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub provenance: Option<crate::ProvenanceReport>,
35    pub repair_target: Option<String>,
36    pub repaired: bool,
37    pub repairs: Vec<FsckRepair>,
38}
39
40impl FsckReport {
41    pub const CONTRACT: ReportContract = ReportContract {
42        schema_name: "maintenance fsck",
43        machine_output_kind: MachineOutputKind::Json,
44        output_discriminator: None,
45        schema: schema_for_report::<FsckReport>,
46    };
47}
48
49impl HeddleReport for FsckReport {
50    const CONTRACT: ReportContract = FsckReport::CONTRACT;
51}
52
53#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
54pub struct FsckRepair {
55    pub name: String,
56    pub repaired: bool,
57    pub detail: String,
58    pub count: usize,
59}
60
61#[derive(Debug, Clone, Serialize, JsonSchema, PartialEq, Eq)]
62pub struct FsckError {
63    pub kind: String,
64    pub message: String,
65    pub object: Option<String>,
66}
67
68fn make_error(kind: &str, message: &str, object: Option<String>) -> FsckError {
69    FsckError {
70        kind: kind.to_string(),
71        message: message.to_string(),
72        object,
73    }
74}
75
76pub fn fsck(ctx: &ExecutionContext, opts: FsckOptions) -> Result<FsckReport> {
77    let repo = ctx.require_repo()?;
78
79    let mut errors: Vec<FsckError> = Vec::new();
80    let mut warnings: Vec<String> = Vec::new();
81    let mut objects_checked: usize = 0;
82
83    state::check_states(repo, &mut errors, &mut objects_checked, opts.thorough)?;
84
85    let provenance = (opts.thorough && opts.provenance)
86        .then(|| crate::verify_repository_provenance(repo))
87        .transpose()?;
88    if let Some(provenance) = &provenance {
89        for state in &provenance.states {
90            match (state.status.as_str(), state.failed_link.as_deref()) {
91                ("Legacy", _) => errors.push(make_error(
92                    "legacy_provenance",
93                    &format!(
94                        "State {} Legacy at content link: {}",
95                        state.state_id, state.detail
96                    ),
97                    Some(state.state_id.clone()),
98                )),
99                (_, Some(_)) => errors.push(make_error(
100                    "invalid_provenance_chain",
101                    &format!(
102                        "State {} {}: {}",
103                        state.state_id,
104                        state.display_status(),
105                        state.detail
106                    ),
107                    Some(state.state_id.clone()),
108                )),
109                _ => {}
110            }
111        }
112    }
113
114    if opts.full {
115        objects::check_tree_objects(repo, &mut errors, &mut warnings, &mut objects_checked)?;
116    }
117
118    refs::check_refs(repo, &mut errors, &mut warnings)?;
119    refs::check_merge_state(repo, &mut warnings)?;
120    if opts.git_projection {
121        git_projection::check_git_projection(
122            repo,
123            &mut errors,
124            &mut warnings,
125            &mut objects_checked,
126        )?;
127    }
128
129    let valid = errors.is_empty();
130
131    Ok(FsckReport {
132        valid,
133        errors,
134        warnings,
135        objects_checked,
136        git_projection_checked: opts.git_projection,
137        provenance,
138        repair_target: None,
139        repaired: false,
140        repairs: Vec::new(),
141    })
142}
143
144fn invalid_fsck_config(message: impl Into<String>) -> HeddleError {
145    HeddleError::Config(message.into())
146}