securegit 0.8.5

Zero-trust git replacement with 12 built-in security scanners, LLM redteam bridge, universal undo, durable backups, and a 50-tool MCP server
Documentation
use crate::cli::UI;
use crate::core::SanitizeConfig;
use crate::git::GitSanitizer;
use anyhow::Result;
use std::path::PathBuf;

pub async fn execute(path: PathBuf, dry_run: bool, backup: bool, ui: &UI) -> Result<()> {
    ui.header("Sanitization");
    ui.blank();

    let git_dir = if path.ends_with(".git") {
        path.clone()
    } else {
        path.join(".git")
    };

    if !git_dir.exists() {
        anyhow::bail!("Not a git repository: {}", path.display());
    }

    ui.field("Repository", path.display());
    ui.field("Git dir", git_dir.display());
    ui.blank();

    if dry_run {
        ui.warning("DRY RUN - No changes will be made");
        ui.blank();
    }

    if backup && !dry_run {
        let backup_path = git_dir.with_extension("git.bak");
        ui.info(format!("Creating backup: {}", backup_path.display()));
        copy_dir_recursive(&git_dir, &backup_path)?;
        ui.success(format!("Backup created: {}", backup_path.display()));
    }

    let sanitizer = GitSanitizer::new(SanitizeConfig::default());

    if !dry_run {
        let report = sanitizer.sanitize(&git_dir)?;

        ui.result_banner(
            true,
            "Sanitization complete",
            &[
                ("Hooks removed", report.removed_hooks.len().to_string()),
                (
                    "Config keys removed",
                    report.removed_config_keys.len().to_string(),
                ),
                (
                    "Config sections removed",
                    report.removed_config_sections.len().to_string(),
                ),
            ],
        );

        if !report.warnings.is_empty() {
            ui.section("Warnings:");
            for warning in &report.warnings {
                ui.warning(warning);
            }
        }
    } else {
        ui.info(format!("Would sanitize: {}", git_dir.display()));
    }

    ui.blank();
    Ok(())
}

fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> anyhow::Result<()> {
    std::fs::create_dir_all(dst)?;
    for entry in walkdir::WalkDir::new(src)
        .into_iter()
        .filter_map(|e| e.ok())
    {
        let rel = entry
            .path()
            .strip_prefix(src)
            .map_err(|e| anyhow::anyhow!("Failed to compute relative path: {}", e))?;
        let target = dst.join(rel);
        if entry.file_type().is_dir() {
            std::fs::create_dir_all(&target)?;
        } else {
            std::fs::copy(entry.path(), &target)?;
        }
    }
    Ok(())
}