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 anyhow::{bail, Result};
use git2::StatusOptions;
use std::path::Path;

pub fn execute(
    path: &Path,
    force: bool,
    directories: bool,
    dry_run: bool,
    remove_ignored: bool,
    ui: &UI,
) -> Result<()> {
    if !force && !dry_run {
        bail!(
            "clean requires -f (force) or -n (dry-run).\n\
             Use securegit clean -f to remove untracked files."
        );
    }

    let repo = crate::ops::open_repo(path)?;
    let mut opts = StatusOptions::new();
    opts.include_untracked(true)
        .recurse_untracked_dirs(true)
        .exclude_submodules(true);

    // -x flag: include ignored files in removal
    opts.include_ignored(remove_ignored);

    let statuses = repo.statuses(Some(&mut opts))?;
    let mut removed = 0;

    for entry in statuses.iter() {
        let dominated = entry.status();
        let is_untracked = dominated.contains(git2::Status::WT_NEW);
        let is_ignored = dominated.contains(git2::Status::IGNORED);

        if !is_untracked && !is_ignored {
            continue;
        }
        // Without -x, only remove untracked (non-ignored) files
        if is_ignored && !remove_ignored {
            continue;
        }

        let file_path = entry.path().unwrap_or("");
        let full = path.join(file_path);

        if full.is_dir() && !directories {
            continue;
        }

        if dry_run {
            ui.list_item(format!("Would remove {}", file_path));
        } else if full.is_dir() {
            std::fs::remove_dir_all(&full)?;
            ui.success(format!("Removing {}/", file_path));
        } else if full.exists() {
            std::fs::remove_file(&full)?;
            ui.success(format!("Removing {}", file_path));
        }
        removed += 1;
    }

    if removed == 0 {
        ui.info("Nothing to clean");
    }

    Ok(())
}