use std::collections::HashMap;
use anyhow::Result;
use crate::{cli::banner, cli::output::*, config::CONFIG, git::GitRepo};
pub async fn hooks_status() -> Result<()> {
hooks_status_at(None).await
}
pub async fn hooks_status_at(repo_path: Option<&std::path::Path>) -> Result<()> {
banner::print_banner(None);
let repo = match repo_path {
Some(path) => match GitRepo::open(path) {
Ok(repo) => {
styled!("{} Git repository detected", ("✅", "success_symbol"));
repo
}
Err(_) => {
styled!(
"{} Not a git repository: {}",
("❌", "error_symbol"),
(path.display().to_string(), "path")
);
return Ok(());
}
},
None => match GitRepo::discover() {
Ok(repo) => {
styled!("{} Git repository detected", ("✅", "success_symbol"));
repo
}
Err(_) => {
styled!("{} Not in a git repository", ("❌", "error_symbol"));
styled!("Run this command from within a git repository");
return Ok(());
}
},
};
let hooks_config = &CONFIG.hooks;
styled!("");
styled!("{} {}:", ("📋", "info_symbol"), ("Settings", "branch"));
styled!(
" Skip all: {}",
(hooks_config.skip_all.to_string(), "property")
);
styled!(
" Parallel execution: {}",
(hooks_config.parallel.to_string(), "property")
);
styled!(
" Verbose level: {}",
(CONFIG.general.verbose.to_string(), "property")
);
let hooks_dir = repo.git_dir().join("hooks");
let supported_hooks = ["pre-commit", "commit-msg", "post-checkout", "pre-push"];
let mut hook_status = HashMap::new();
styled!("{} Checking hook installation...", ("🔍", "info_symbol"));
for hook_name in &supported_hooks {
let hook_path = hooks_dir.join(hook_name);
if hook_path.exists() {
if let Ok(content) = std::fs::read_to_string(&hook_path) {
if content.contains("guardy hooks run") {
hook_status.insert(*hook_name, "installed");
styled!(" {} {}", ("✅", "success_symbol"), (hook_name, "property"));
} else {
hook_status.insert(*hook_name, "conflict");
styled!(
" {} {} (managed by other tool)",
("⚠️", "warning_symbol"),
(hook_name, "property")
);
}
} else {
hook_status.insert(*hook_name, "error");
styled!(
" {} {} (unreadable)",
("❌", "error_symbol"),
(hook_name, "property")
);
}
} else {
hook_status.insert(*hook_name, "missing");
styled!(
" {} {} (not installed)",
("❌", "error_symbol"),
(hook_name, "property")
);
}
}
let installed_count = hook_status
.values()
.filter(|&&status| status == "installed")
.count();
let conflict_count = hook_status
.values()
.filter(|&&status| status == "conflict")
.count();
let missing_count = hook_status
.values()
.filter(|&&status| status == "missing")
.count();
if installed_count > 0 {
styled!("");
styled!("{} {}:", ("⚙️ ", "info_symbol"), ("Hooks", "branch"));
let verbose = CONFIG.general.verbose;
if hook_status.get("pre-commit") == Some(&"installed") {
let pre_commit = &hooks_config.pre_commit;
styled!(" pre-commit:");
styled!(" Skip: {}", (pre_commit.skip.to_string(), "property"));
styled!(
" Parallel: {}",
(pre_commit.parallel.to_string(), "property")
);
if verbose > 0 {
styled!(
" Commands: {}",
(pre_commit.commands.len().to_string(), "number")
);
for (name, command) in &pre_commit.commands {
styled!(
" {}: {}",
(name, "property"),
(&command.run, "command")
);
}
} else {
let command_names: Vec<&str> =
pre_commit.commands.keys().map(|s| s.as_str()).collect();
styled!(" Commands: {}", (command_names.join(", "), "property"));
}
}
if hook_status.get("commit-msg") == Some(&"installed") {
let commit_msg = &hooks_config.commit_msg;
styled!(" commit-msg:");
styled!(" Skip: {}", (commit_msg.skip.to_string(), "property"));
if verbose > 0 {
styled!(
" Commands: {}",
(commit_msg.commands.len().to_string(), "number")
);
for (name, command) in &commit_msg.commands {
styled!(
" {}: {}",
(name, "property"),
(&command.run, "command")
);
}
} else {
let command_names: Vec<&str> =
commit_msg.commands.keys().map(|s| s.as_str()).collect();
if command_names.is_empty() {
styled!(" Commands: (builtin only)", ("property", "property"));
} else {
styled!(" Commands: {}", (command_names.join(", "), "property"));
}
}
}
if hook_status.get("pre-push") == Some(&"installed") {
let pre_push = &hooks_config.pre_push;
styled!(" pre-push:");
styled!(" Skip: {}", (pre_push.skip.to_string(), "property"));
styled!(
" Parallel: {}",
(pre_push.parallel.to_string(), "property")
);
if verbose > 0 {
styled!(
" Commands: {}",
(pre_push.commands.len().to_string(), "number")
);
for (name, command) in &pre_push.commands {
styled!(
" {}: {}",
(name, "property"),
(&command.run, "command")
);
}
} else {
let command_names: Vec<&str> =
pre_push.commands.keys().map(|s| s.as_str()).collect();
styled!(" Commands: {}", (command_names.join(", "), "property"));
}
}
if hook_status.get("post-checkout") == Some(&"installed") {
let post_checkout = &hooks_config.post_checkout;
styled!(" post-checkout:");
styled!(" Skip: {}", (post_checkout.skip.to_string(), "property"));
if verbose > 0 {
styled!(
" Commands: {}",
(post_checkout.commands.len().to_string(), "number")
);
for (name, command) in &post_checkout.commands {
styled!(
" {}: {}",
(name, "property"),
(&command.run, "command")
);
}
} else {
let command_names: Vec<&str> =
post_checkout.commands.keys().map(|s| s.as_str()).collect();
styled!(" Commands: {}", (command_names.join(", "), "property"));
}
}
}
styled!("{} Summary", ("📊", "info_symbol"));
styled!(
" Installed: {}/{}",
(installed_count.to_string(), "number"),
(supported_hooks.len().to_string(), "number")
);
if conflict_count > 0 {
styled!(" Conflicts: {}", (conflict_count.to_string(), "warning"));
}
if missing_count > 0 {
styled!(" Missing: {}", (missing_count.to_string(), "error"));
styled!(
" Run {} to install missing hooks",
("'guardy hooks install'", "command")
);
}
if installed_count == supported_hooks.len() {
styled!(
"{} All hooks are installed and ready!",
("🎉", "success_symbol")
);
} else if installed_count > 0 {
styled!(
"{} Hooks are partially configured",
("⚠️", "warning_symbol")
);
} else {
styled!("{} No hooks are installed", ("❌", "error_symbol"));
}
Ok(())
}