1mod 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}