//! `git fsck` on one repo: the integrity check behind the scheduled and manual fsck runs. use std::path::Path; use crate::command::{GitCommand, GitEnv}; use crate::error::Result; /// Outcome of [`fsck`]. `output` holds git's diagnostics (stdout and stderr, trimmed). It is /// empty when the repo is clean. #[derive(Debug, Clone, PartialEq, Eq)] pub struct FsckReport { pub ok: bool, pub output: String, } /// Runs `git fsck` (with `--full` when asked) on the repo at `repo`. A repo git cannot open, or /// one with damaged objects, is a report with `ok` false, not an error. Only a failure to start /// git is an error. pub async fn fsck(env: &GitEnv, repo: &Path, full: bool) -> Result { let mut cmd = GitCommand::new(env, "fsck").git_dir(repo); if full { cmd = cmd.arg("--full"); } let out = cmd.into_tokio().output().await?; let mut text = String::from_utf8_lossy(&out.stdout).into_owned(); text.push_str(&String::from_utf8_lossy(&out.stderr)); Ok(FsckReport { ok: out.status.success(), output: text.trim().to_owned(), }) } #[cfg(test)] mod tests { use super::*; use std::process::Command; fn env(dir: &Path) -> GitEnv { GitEnv { git_bin: "git".into(), data_dir: dir.to_path_buf(), } } #[tokio::test] async fn clean_repo_passes_and_damaged_repo_fails() { let dir = tempfile::tempdir().expect("tempdir"); let work = dir.path().join("work"); std::fs::create_dir_all(&work).expect("mkdir"); let run = |args: &[&str]| { let out = Command::new("git") .current_dir(&work) .env("GIT_AUTHOR_NAME", "t") .env("GIT_AUTHOR_EMAIL", "t@example.invalid") .env("GIT_COMMITTER_NAME", "t") .env("GIT_COMMITTER_EMAIL", "t@example.invalid") .args(args) .output() .expect("git"); assert!(out.status.success(), "{args:?}: {out:?}"); }; run(&["init", "-q", "-b", "main"]); run(&["commit", "-q", "--allow-empty", "-m", "one"]); let git_dir = work.join(".git"); let clean = fsck(&env(dir.path()), &git_dir, true).await.expect("fsck"); assert!(clean.ok, "{}", clean.output); // Corrupt the only loose object: fsck must report it, not error out. let head = Command::new("git") .current_dir(&work) .args(["rev-parse", "HEAD"]) .output() .expect("rev-parse"); let oid = String::from_utf8_lossy(&head.stdout).trim().to_owned(); let object = git_dir.join("objects").join(&oid[..2]).join(&oid[2..]); // Git stores loose objects read-only, so replace the file rather than writing into it. std::fs::remove_file(&object).expect("remove object"); std::fs::write(&object, b"not zlib").expect("corrupt"); let damaged = fsck(&env(dir.path()), &git_dir, false).await.expect("fsck"); assert!(!damaged.ok); assert!(!damaged.output.is_empty()); let missing = fsck(&env(dir.path()), &dir.path().join("nope.git"), false) .await .expect("fsck"); assert!(!missing.ok); } }