forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
3.2 KiB
1//! `git fsck` on one repo: the integrity check behind the scheduled and manual fsck runs.
2
3use std::path::Path;
4
5use crate::command::{GitCommand, GitEnv};
6use crate::error::Result;
7
8/// Outcome of [`fsck`]. `output` holds git's diagnostics (stdout and stderr, trimmed). It is
9/// empty when the repo is clean.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct FsckReport {
12 pub ok: bool,
13 pub output: String,
14}
15
16/// Runs `git fsck` (with `--full` when asked) on the repo at `repo`. A repo git cannot open, or
17/// one with damaged objects, is a report with `ok` false, not an error. Only a failure to start
18/// git is an error.
19pub async fn fsck(env: &GitEnv, repo: &Path, full: bool) -> Result<FsckReport> {
20 let mut cmd = GitCommand::new(env, "fsck").git_dir(repo);
21 if full {
22 cmd = cmd.arg("--full");
23 }
24 let out = cmd.into_tokio().output().await?;
25 let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
26 text.push_str(&String::from_utf8_lossy(&out.stderr));
27 Ok(FsckReport {
28 ok: out.status.success(),
29 output: text.trim().to_owned(),
30 })
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36 use std::process::Command;
37
38 fn env(dir: &Path) -> GitEnv {
39 GitEnv {
40 git_bin: "git".into(),
41 data_dir: dir.to_path_buf(),
42 }
43 }
44
45 #[tokio::test]
46 async fn clean_repo_passes_and_damaged_repo_fails() {
47 let dir = tempfile::tempdir().expect("tempdir");
48 let work = dir.path().join("work");
49 std::fs::create_dir_all(&work).expect("mkdir");
50 let run = |args: &[&str]| {
51 let out = Command::new("git")
52 .current_dir(&work)
53 .env("GIT_AUTHOR_NAME", "t")
54 .env("GIT_AUTHOR_EMAIL", "t@example.invalid")
55 .env("GIT_COMMITTER_NAME", "t")
56 .env("GIT_COMMITTER_EMAIL", "t@example.invalid")
57 .args(args)
58 .output()
59 .expect("git");
60 assert!(out.status.success(), "{args:?}: {out:?}");
61 };
62 run(&["init", "-q", "-b", "main"]);
63 run(&["commit", "-q", "--allow-empty", "-m", "one"]);
64 let git_dir = work.join(".git");
65
66 let clean = fsck(&env(dir.path()), &git_dir, true).await.expect("fsck");
67 assert!(clean.ok, "{}", clean.output);
68
69 // Corrupt the only loose object: fsck must report it, not error out.
70 let head = Command::new("git")
71 .current_dir(&work)
72 .args(["rev-parse", "HEAD"])
73 .output()
74 .expect("rev-parse");
75 let oid = String::from_utf8_lossy(&head.stdout).trim().to_owned();
76 let object = git_dir.join("objects").join(&oid[..2]).join(&oid[2..]);
77 // Git stores loose objects read-only, so replace the file rather than writing into it.
78 std::fs::remove_file(&object).expect("remove object");
79 std::fs::write(&object, b"not zlib").expect("corrupt");
80 let damaged = fsck(&env(dir.path()), &git_dir, false).await.expect("fsck");
81 assert!(!damaged.ok);
82 assert!(!damaged.output.is_empty());
83
84 let missing = fsck(&env(dir.path()), &dir.path().join("nope.git"), false)
85 .await
86 .expect("fsck");
87 assert!(!missing.ok);
88 }
89}