guardy 0.2.4

Fast, secure git hooks in Rust with secret scanning and protected file synchronization
Documentation
use std::collections::HashMap;

use anyhow::Result;

use crate::{cli::banner, cli::output::*, config::CONFIG, git::GitRepo};

/// Display detailed status of hooks installation and configuration
pub async fn hooks_status() -> Result<()> {
    hooks_status_at(None).await
}

/// Display detailed status of hooks installation and configuration at a specific path (for testing)
pub async fn hooks_status_at(repo_path: Option<&std::path::Path>) -> Result<()> {
    // Print banner without context
    banner::print_banner(None);

    // Check if we're in a git repository
    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(());
            }
        },
    };

    // Show hooks configuration from global CONFIG
    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")
    );

    // Check hook installation status
    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")
            );
        }
    }

    // Count status types
    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();

    // Show hook configuration details for each installed hook
    if installed_count > 0 {
        styled!("");
        styled!("{} {}:", ("⚙️ ", "info_symbol"), ("Hooks", "branch"));
        let verbose = CONFIG.general.verbose;

        // Show pre-commit configuration
        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 {
                // Verbose: Show count and full command details
                styled!(
                    "    Commands: {}",
                    (pre_commit.commands.len().to_string(), "number")
                );
                for (name, command) in &pre_commit.commands {
                    styled!(
                        "      {}: {}",
                        (name, "property"),
                        (&command.run, "command")
                    );
                }
            } else {
                // Default: Show command names only
                let command_names: Vec<&str> =
                    pre_commit.commands.keys().map(|s| s.as_str()).collect();
                styled!("    Commands: {}", (command_names.join(", "), "property"));
            }
        }

        // Show commit-msg configuration
        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 {
                // Verbose: Show count and full command details
                styled!(
                    "    Commands: {}",
                    (commit_msg.commands.len().to_string(), "number")
                );
                for (name, command) in &commit_msg.commands {
                    styled!(
                        "      {}: {}",
                        (name, "property"),
                        (&command.run, "command")
                    );
                }
            } else {
                // Default: Show command names only
                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"));
                }
            }
        }

        // Show pre-push configuration
        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 {
                // Verbose: Show count and full command details
                styled!(
                    "    Commands: {}",
                    (pre_push.commands.len().to_string(), "number")
                );
                for (name, command) in &pre_push.commands {
                    styled!(
                        "      {}: {}",
                        (name, "property"),
                        (&command.run, "command")
                    );
                }
            } else {
                // Default: Show command names only
                let command_names: Vec<&str> =
                    pre_push.commands.keys().map(|s| s.as_str()).collect();
                styled!("    Commands: {}", (command_names.join(", "), "property"));
            }
        }

        // Show post-checkout configuration
        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 {
                // Verbose: Show count and full command details
                styled!(
                    "    Commands: {}",
                    (post_checkout.commands.len().to_string(), "number")
                );
                for (name, command) in &post_checkout.commands {
                    styled!(
                        "      {}: {}",
                        (name, "property"),
                        (&command.run, "command")
                    );
                }
            } else {
                // Default: Show command names only
                let command_names: Vec<&str> =
                    post_checkout.commands.keys().map(|s| s.as_str()).collect();
                styled!("    Commands: {}", (command_names.join(", "), "property"));
            }
        }
    }

    // Summary
    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")
        );
    }

    // Overall status
    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(())
}