use std::path::Path;
use serde::Serialize;
use void_core::ops::fsck::{self as core_fsck, FsckError, FsckOptions, FsckWarning};
use crate::context::{open_repo, void_err_to_cli};
use crate::output::{run_command, CliError, CliOptions};
#[derive(Debug)]
pub struct FsckArgs {
pub full: bool,
pub unreferenced: bool,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FsckOutput {
pub ok: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub stats: FsckStatsOutput,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FsckStatsOutput {
pub objects_checked: usize,
pub objects_valid: usize,
pub objects_missing: usize,
pub objects_corrupt: usize,
}
fn format_error(err: &FsckError) -> String {
match err {
FsckError::MissingObject { cid, referenced_by } => {
format!("MissingObject: CID {} referenced by {}", cid, referenced_by)
}
FsckError::CorruptObject { cid, reason } => {
format!("CorruptObject: CID {}: {}", cid, reason)
}
FsckError::InvalidHead { reason } => {
format!("InvalidHead: {}", reason)
}
FsckError::InvalidIndex { reason } => {
format!("InvalidIndex: {}", reason)
}
FsckError::OrphanedLock => "OrphanedLock: Stale lock file exists".to_string(),
FsckError::DataLoss {
commit_cid,
parent_cid,
commit_files,
parent_files,
} => {
format!(
"DataLoss: Commit {} has {} files, parent {} had {}",
commit_cid, commit_files, parent_cid, parent_files
)
}
}
}
fn format_warning(warn: &FsckWarning) -> String {
match warn {
FsckWarning::UnreferencedObject { cid } => {
format!(
"UnreferencedObject: CID {} is unreferenced (gc candidate)",
cid
)
}
FsckWarning::StaleManifest => "StaleManifest: Manifest file is stale".to_string(),
FsckWarning::UnexpectedFileDrop {
commit_cid,
parent_cid,
commit_files,
parent_files,
} => {
format!(
"UnexpectedFileDrop: Commit {} has {} files, parent {} had {}",
commit_cid, commit_files, parent_cid, parent_files
)
}
}
}
pub fn run(cwd: &Path, args: FsckArgs, opts: &CliOptions) -> Result<(), CliError> {
run_command("fsck", opts, |ctx| {
ctx.progress("Running repository integrity check...");
let repo = open_repo(cwd)?;
let options = FsckOptions {
find_unreferenced: args.unreferenced,
verify_content_hashes: args.full,
observer: None,
};
let result = core_fsck::fsck(repo.context(), &options).map_err(void_err_to_cli)?;
if !ctx.use_json() {
if result.ok {
ctx.info("Repository is healthy.");
} else {
ctx.info("Repository has issues:");
}
if !result.errors.is_empty() {
ctx.info(format!("\nErrors ({}):", result.errors.len()));
for err in &result.errors {
let formatted = format_error(err);
ctx.info(format!(" - {}", formatted));
}
}
if !result.warnings.is_empty() {
ctx.info(format!("\nWarnings ({}):", result.warnings.len()));
for warn in &result.warnings {
let formatted = format_warning(warn);
ctx.info(format!(" - {}", formatted));
}
}
ctx.info(format!(
"\nStatistics: {} objects checked, {} valid, {} missing, {} corrupt",
result.stats.objects_checked,
result.stats.objects_valid,
result.stats.objects_missing,
result.stats.objects_corrupt
));
}
Ok(FsckOutput {
ok: result.ok,
errors: result.errors.iter().map(format_error).collect(),
warnings: result.warnings.iter().map(format_warning).collect(),
stats: FsckStatsOutput {
objects_checked: result.stats.objects_checked,
objects_valid: result.stats.objects_valid,
objects_missing: result.stats.objects_missing,
objects_corrupt: result.stats.objects_corrupt,
},
})
})
}