git-cli 0.3.0

A CLI tool that translates natural-language task descriptions into git commands using a local Ollama LLM
use std::process::Command;

#[derive(Debug)]
pub struct GitContext {
    pub is_repo: bool,
    pub branch: Option<String>,
    pub status: Option<String>,
    pub recent_log: Option<String>,
    pub remotes: Option<String>,
}

impl GitContext {
    pub fn gather() -> Self {
        let is_repo = run_git(&["rev-parse", "--is-inside-work-tree"])
            .map(|s| s.trim() == "true")
            .unwrap_or(false);

        if !is_repo {
            return Self {
                is_repo: false,
                branch: None,
                status: None,
                recent_log: None,
                remotes: None,
            };
        }

        Self {
            is_repo: true,
            branch: run_git(&["rev-parse", "--abbrev-ref", "HEAD"]).map(|s| s.trim().to_string()),
            status: run_git(&["status", "--porcelain"]),
            recent_log: run_git(&["log", "--oneline", "-10"]),
            remotes: run_git(&["remote", "-v"]),
        }
    }

    pub fn summary(&self) -> String {
        if !self.is_repo {
            return "Not inside a git repository.".to_string();
        }

        let mut parts = Vec::new();

        if let Some(ref branch) = self.branch {
            parts.push(format!("Current branch: {branch}"));
        }

        if let Some(ref status) = self.status {
            if status.trim().is_empty() {
                parts.push("Working tree: clean".to_string());
            } else {
                parts.push(format!("Working tree status:\n{status}"));
            }
        }

        if let Some(ref log) = self.recent_log {
            if !log.trim().is_empty() {
                parts.push(format!("Recent commits:\n{log}"));
            }
        }

        if let Some(ref remotes) = self.remotes {
            if !remotes.trim().is_empty() {
                parts.push(format!("Remotes:\n{remotes}"));
            }
        }

        parts.join("\n\n")
    }
}

fn run_git(args: &[&str]) -> Option<String> {
    Command::new("git")
        .args(args)
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
}