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::Result;
use git2::StatusOptions;
use serde_json::json;
use std::path::Path;

pub fn execute(path: &Path, short: bool, verbose: bool, ui: &UI) -> Result<()> {
    let repo = crate::ops::open_repo(path)?;
    let head = repo.head().ok();
    let branch_name = head
        .as_ref()
        .and_then(|h| h.shorthand().map(|s| s.to_string()))
        .unwrap_or_else(|| "(detached)".to_string());

    if !short {
        ui.info(format!("On branch {}", branch_name));
        ui.blank();
    }

    let mut opts = StatusOptions::new();
    opts.include_untracked(true)
        .recurse_untracked_dirs(true)
        .renames_head_to_index(true);

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

    if statuses.is_empty() {
        if !short {
            ui.success("nothing to commit, working tree clean");
        }
        return Ok(());
    }

    let mut staged = Vec::new();
    let mut unstaged = Vec::new();
    let mut untracked = Vec::new();

    for entry in statuses.iter() {
        let status = entry.status();
        let path_str = entry.path().unwrap_or("").to_string();

        if status.intersects(
            git2::Status::INDEX_NEW
                | git2::Status::INDEX_MODIFIED
                | git2::Status::INDEX_DELETED
                | git2::Status::INDEX_RENAMED
                | git2::Status::INDEX_TYPECHANGE,
        ) {
            let prefix = if status.contains(git2::Status::INDEX_NEW) {
                "new file"
            } else if status.contains(git2::Status::INDEX_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::INDEX_DELETED) {
                "deleted"
            } else if status.contains(git2::Status::INDEX_RENAMED) {
                "renamed"
            } else {
                "typechange"
            };
            staged.push((prefix, path_str.clone()));
        }

        if status.intersects(
            git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
        ) {
            let prefix = if status.contains(git2::Status::WT_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::WT_DELETED) {
                "deleted"
            } else {
                "renamed"
            };
            unstaged.push((prefix, path_str.clone()));
        }

        if status.contains(git2::Status::WT_NEW) {
            untracked.push(path_str);
        }
    }

    if short {
        for (prefix, path) in &staged {
            let code = match *prefix {
                "new file" => "A",
                "modified" => "M",
                "deleted" => "D",
                "renamed" => "R",
                _ => "T",
            };
            println!("{} {}", code, path);
        }
        for (prefix, path) in &unstaged {
            let code = match *prefix {
                "modified" => " M",
                "deleted" => " D",
                _ => " R",
            };
            println!("{} {}", code, path);
        }
        for path in &untracked {
            println!("?? {}", path);
        }
        return Ok(());
    }

    if !staged.is_empty() {
        ui.section("Changes staged for commit:");
        for (prefix, path) in &staged {
            ui.status_item(true, format!("{:12} {}", prefix, path));
        }
        ui.blank();
    }

    if !unstaged.is_empty() {
        ui.section("Changes not staged:");
        for (prefix, path) in &unstaged {
            ui.status_item(false, format!("{:12} {}", prefix, path));
        }
        ui.blank();
    }

    if !untracked.is_empty() {
        ui.section("Untracked files:");
        for path in &untracked {
            ui.list_item(path);
        }
        ui.blank();
    }

    if verbose {
        ui.header("Changes staged for commit:");
        crate::ops::diff::execute(
            path,
            &crate::ops::diff::DiffDisplayOptions {
                cached: true,
                stat_only: false,
                name_only: false,
                name_status: false,
                commit_spec: None,
                paths: vec![],
                ignore_whitespace: false,
            },
            ui,
        )?;

        ui.header("Changes not staged for commit:");
        crate::ops::diff::execute(
            path,
            &crate::ops::diff::DiffDisplayOptions {
                cached: false,
                stat_only: false,
                name_only: false,
                name_status: false,
                commit_spec: None,
                paths: vec![],
                ignore_whitespace: false,
            },
            ui,
        )?;
    }

    Ok(())
}

pub fn execute_compact(path: &Path) -> Result<String> {
    use crate::cli::compact::format_compact_status;

    let repo = crate::ops::open_repo(path)?;
    let head = repo.head().ok();
    let branch_name = head
        .as_ref()
        .and_then(|h| h.shorthand().map(|s| s.to_string()))
        .unwrap_or_else(|| "(detached)".to_string());

    let mut opts = StatusOptions::new();
    opts.include_untracked(true)
        .recurse_untracked_dirs(true)
        .renames_head_to_index(true);

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

    let mut staged = Vec::new();
    let mut unstaged = Vec::new();
    let mut untracked = Vec::new();

    for entry in statuses.iter() {
        let status = entry.status();
        let path_str = entry.path().unwrap_or("").to_string();

        if status.intersects(
            git2::Status::INDEX_NEW
                | git2::Status::INDEX_MODIFIED
                | git2::Status::INDEX_DELETED
                | git2::Status::INDEX_RENAMED
                | git2::Status::INDEX_TYPECHANGE,
        ) {
            let prefix = if status.contains(git2::Status::INDEX_NEW) {
                "new file"
            } else if status.contains(git2::Status::INDEX_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::INDEX_DELETED) {
                "deleted"
            } else if status.contains(git2::Status::INDEX_RENAMED) {
                "renamed"
            } else {
                "typechange"
            };
            staged.push((prefix, path_str.clone()));
        }

        if status.intersects(
            git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
        ) {
            let prefix = if status.contains(git2::Status::WT_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::WT_DELETED) {
                "deleted"
            } else {
                "renamed"
            };
            unstaged.push((prefix, path_str.clone()));
        }

        if status.contains(git2::Status::WT_NEW) {
            untracked.push(path_str);
        }
    }

    Ok(format_compact_status(
        &branch_name,
        &staged,
        &unstaged,
        &untracked,
    ))
}

pub fn execute_json(path: &Path) -> Result<()> {
    let repo = crate::ops::open_repo(path)?;
    let head = repo.head().ok();
    let branch_name = head
        .as_ref()
        .and_then(|h| h.shorthand().map(|s| s.to_string()))
        .unwrap_or_else(|| "(detached)".to_string());

    let mut opts = StatusOptions::new();
    opts.include_untracked(true)
        .recurse_untracked_dirs(true)
        .renames_head_to_index(true);

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

    let mut staged = Vec::new();
    let mut unstaged = Vec::new();
    let mut untracked = Vec::new();

    for entry in statuses.iter() {
        let status = entry.status();
        let path_str = entry.path().unwrap_or("").to_string();

        if status.intersects(
            git2::Status::INDEX_NEW
                | git2::Status::INDEX_MODIFIED
                | git2::Status::INDEX_DELETED
                | git2::Status::INDEX_RENAMED
                | git2::Status::INDEX_TYPECHANGE,
        ) {
            let kind = if status.contains(git2::Status::INDEX_NEW) {
                "new"
            } else if status.contains(git2::Status::INDEX_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::INDEX_DELETED) {
                "deleted"
            } else if status.contains(git2::Status::INDEX_RENAMED) {
                "renamed"
            } else {
                "typechange"
            };
            staged.push(json!({"status": kind, "path": path_str}));
        }

        if status.intersects(
            git2::Status::WT_MODIFIED | git2::Status::WT_DELETED | git2::Status::WT_RENAMED,
        ) {
            let kind = if status.contains(git2::Status::WT_MODIFIED) {
                "modified"
            } else if status.contains(git2::Status::WT_DELETED) {
                "deleted"
            } else {
                "renamed"
            };
            unstaged.push(json!({"status": kind, "path": path_str}));
        }

        if status.contains(git2::Status::WT_NEW) {
            untracked.push(path_str);
        }
    }

    println!(
        "{}",
        json!({
            "branch": branch_name,
            "staged": staged,
            "unstaged": unstaged,
            "untracked": untracked,
            "clean": staged.is_empty() && unstaged.is_empty() && untracked.is_empty(),
        })
    );
    Ok(())
}