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::ops::oplog;
use anyhow::Result;
use std::path::Path;

pub fn save(path: &Path, message: Option<&str>, ui: &UI) -> Result<()> {
    let desc = format!("stash save '{}'", message.unwrap_or("WIP"));
    oplog::with_oplog(path, "stash", &desc, || save_inner(path, message, ui))
}

fn save_inner(path: &Path, message: Option<&str>, ui: &UI) -> Result<()> {
    let mut repo = crate::ops::open_repo(path)?;
    let sig = repo.signature()?;
    let msg = message.unwrap_or("WIP on stash");

    let flags = git2::StashFlags::DEFAULT;
    repo.stash_save(&sig, msg, Some(flags))?;

    ui.success(format!("Saved working directory: {}", msg));
    Ok(())
}

pub fn pop(path: &Path, index: usize, ui: &UI) -> Result<()> {
    oplog::with_oplog(path, "stash", &format!("pop stash@{{{}}}", index), || {
        pop_inner(path, index, ui)
    })
}

fn pop_inner(path: &Path, index: usize, ui: &UI) -> Result<()> {
    let mut repo = crate::ops::open_repo(path)?;
    repo.stash_pop(index, None)?;
    ui.success(format!("Dropped stash@{{{}}}", index));
    Ok(())
}

pub fn apply(path: &Path, index: usize, ui: &UI) -> Result<()> {
    let mut repo = crate::ops::open_repo(path)?;
    repo.stash_apply(index, None)?;
    ui.success(format!("Applied stash@{{{}}}", index));
    Ok(())
}

pub fn list_compact(path: &Path) -> Result<String> {
    let mut repo = crate::ops::open_repo(path)?;
    let mut entries = Vec::new();

    repo.stash_foreach(|index, message, _oid| {
        let msg = message
            .find(": ")
            .map(|i| &message[i + 2..])
            .unwrap_or(message);
        entries.push(format!("stash@{{{}}}: {}", index, msg));
        true
    })?;

    if entries.is_empty() {
        Ok("(no stashes)".to_string())
    } else {
        Ok(entries.join("\n"))
    }
}

pub fn list(path: &Path, ui: &UI) -> Result<()> {
    let mut repo = crate::ops::open_repo(path)?;
    let mut entries = Vec::new();

    repo.stash_foreach(|index, message, _oid| {
        entries.push((index, message.to_string()));
        true
    })?;

    for (index, message) in &entries {
        ui.list_item(format!("stash@{{{}}}: {}", index, message));
    }

    if entries.is_empty() {
        ui.info("No stash entries found");
    }

    Ok(())
}

pub fn drop_stash(path: &Path, index: usize, ui: &UI) -> Result<()> {
    oplog::with_oplog(path, "stash", &format!("drop stash@{{{}}}", index), || {
        drop_inner(path, index, ui)
    })
}

fn drop_inner(path: &Path, index: usize, ui: &UI) -> Result<()> {
    let mut repo = crate::ops::open_repo(path)?;
    repo.stash_drop(index)?;
    ui.success(format!("Dropped stash@{{{}}}", index));
    Ok(())
}