Skip to main content

heddle_cli_render/cli/render/
fsck.rs

1// SPDX-License-Identifier: Apache-2.0
2use anyhow::Result;
3use verbs::FsckReport;
4
5use crate::cli::{render::write_stdout, style};
6
7pub fn fsck_json(report: &FsckReport) -> Result<()> {
8    let mut text = serde_json::to_string(report)?;
9    text.push('\n');
10    write_stdout(&text)
11}
12
13pub fn fsck_text(report: &FsckReport) -> Result<()> {
14    write_stdout(&format_fsck_text(report))
15}
16
17fn format_fsck_text(report: &FsckReport) -> String {
18    let mut text = String::new();
19    if report.valid {
20        let counted = style::count(report.objects_checked, "object");
21        text.push_str(&format!(
22            "{} repository is valid ({counted} checked)\n",
23            style::ok_marker(),
24        ));
25        if report.git_projection_checked {
26            text.push_str(&format!(
27                "  {}\n",
28                style::field("Git projection", "mapping, notes, and checkout checked")
29            ));
30        }
31    } else {
32        text.push_str(&format!(
33            "{} repository has {}\n",
34            style::error_marker(),
35            style::count(report.errors.len(), "integrity error")
36        ));
37        for error in &report.errors {
38            if let Some(obj) = &error.object {
39                text.push_str(&format!(
40                    "  {} {} {}\n",
41                    style::error(&format!("[{}]", style::human_text(&error.kind))),
42                    style::human_text(&error.message),
43                    style::dim(&format!("({})", style::human_text(obj)))
44                ));
45            } else {
46                text.push_str(&format!(
47                    "  {} {}\n",
48                    style::error(&format!("[{}]", style::human_text(&error.kind))),
49                    style::human_text(&error.message)
50                ));
51            }
52        }
53    }
54    if let Some(target) = &report.repair_target {
55        let status = if report.repaired {
56            "repaired"
57        } else {
58            "no changes"
59        };
60        text.push_str(&format!(
61            "  {}\n",
62            style::field(
63                "Repair",
64                &format!("{}: {status}", style::human_text(target))
65            )
66        ));
67        for repair in &report.repairs {
68            if repair.count > 0 || repair.repaired {
69                text.push_str(&format!(
70                    "    {} {} ({})\n",
71                    style::human_text(&repair.name),
72                    style::human_text(&repair.detail),
73                    repair.count
74                ));
75            }
76        }
77    }
78    if let Some(provenance) = &report.provenance {
79        text.push_str(&format!(
80            "  {}\n",
81            style::field(
82                "Provenance registry",
83                &style::human_text(&provenance.registry_status)
84            )
85        ));
86        for state in &provenance.states {
87            let short = state
88                .state_id
89                .strip_prefix("hs-")
90                .unwrap_or(&state.state_id);
91            let short = &short[..short.len().min(12)];
92            text.push_str(&format!(
93                "    {short} {:<24} {}\n",
94                state.display_status(),
95                style::human_text(&state.detail)
96            ));
97        }
98    }
99    for warning in &report.warnings {
100        text.push_str(&format!(
101            "{} {}\n",
102            style::warn_marker(),
103            style::human_text(warning)
104        ));
105    }
106    text
107}
108
109#[cfg(test)]
110mod tests {
111    use verbs::FsckError;
112
113    use super::*;
114
115    #[test]
116    fn text_renderer_consumes_the_typed_fsck_report() {
117        let report = FsckReport {
118            valid: true,
119            errors: Vec::new(),
120            warnings: vec!["legacy object retained".to_string()],
121            objects_checked: 2,
122            git_projection_checked: false,
123            provenance: None,
124            repair_target: None,
125            repaired: false,
126            repairs: Vec::new(),
127        };
128
129        let text = format_fsck_text(&report);
130        assert!(text.contains("repository is valid (2 objects checked)"));
131        assert!(text.contains("legacy object retained"));
132    }
133
134    #[test]
135    fn text_renderer_shortens_corrupt_object_id() {
136        let id = "a".repeat(64);
137        let report = FsckReport {
138            valid: false,
139            errors: vec![FsckError {
140                kind: "missing_tree".to_string(),
141                message: format!("missing tree object {id}"),
142                object: Some(id.clone()),
143            }],
144            warnings: Vec::new(),
145            objects_checked: 1,
146            git_projection_checked: false,
147            provenance: None,
148            repair_target: None,
149            repaired: false,
150            repairs: Vec::new(),
151        };
152        let text = format_fsck_text(&report);
153        assert!(text.contains("hs-aaaaaaaa"), "{text}");
154        assert!(!text.contains(&id), "{text}");
155        assert!(serde_json::to_string(&report).unwrap().contains(&id));
156    }
157}