ferro_cli/doctor/checks/
dirty_git_tree.rs1use crate::doctor::check::{CheckResult, DoctorCheck};
5use std::path::Path;
6use std::process::Command;
7
8pub struct DirtyGitTreeCheck;
9
10const NAME: &str = "git_clean_and_pushed";
11
12impl DoctorCheck for DirtyGitTreeCheck {
13 fn name(&self) -> &'static str {
14 NAME
15 }
16 fn run(&self, root: &Path) -> CheckResult {
17 check_impl(root)
18 }
19}
20
21pub(crate) fn check_impl(root: &Path) -> CheckResult {
22 if !root.join(".git").exists() {
23 return CheckResult::ok(NAME, "skipped (not a git repo)");
24 }
25
26 let porcelain = match run_git(root, &["status", "--porcelain"]) {
27 Ok(s) => s,
28 Err(e) => return CheckResult::warn(NAME, format!("git status failed: {e}")),
29 };
30 let dirty = !porcelain.trim().is_empty();
31
32 let unpushed = match run_git(root, &["log", "@{upstream}..HEAD", "--oneline"]) {
33 Ok(s) => !s.trim().is_empty(),
34 Err(_) => false, };
36
37 match (dirty, unpushed) {
38 (false, false) => CheckResult::ok(NAME, "working tree clean and pushed"),
39 (true, false) => CheckResult::warn(NAME, "working tree has uncommitted changes"),
40 (false, true) => CheckResult::warn(NAME, "local commits not pushed to upstream"),
41 (true, true) => CheckResult::warn(NAME, "uncommitted changes and unpushed commits present"),
42 }
43}
44
45fn run_git(root: &Path, args: &[&str]) -> Result<String, String> {
46 let out = Command::new("git")
47 .args(args)
48 .current_dir(root)
49 .output()
50 .map_err(|e| e.to_string())?;
51 if !out.status.success() {
52 return Err(String::from_utf8_lossy(&out.stderr).into_owned());
53 }
54 Ok(String::from_utf8_lossy(&out.stdout).into_owned())
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use tempfile::TempDir;
61
62 #[test]
63 fn name_is_git_clean_and_pushed() {
64 assert_eq!(DirtyGitTreeCheck.name(), "git_clean_and_pushed");
65 }
66
67 #[test]
68 fn skipped_when_not_a_git_repo() {
69 let tmp = TempDir::new().unwrap();
70 let r = check_impl(tmp.path());
71 assert_eq!(r.status, crate::doctor::check::CheckStatus::Ok);
72 assert!(r.message.contains("skipped"));
73 }
74}