eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
use anyhow::Result;

#[derive(Debug, Default, Clone)]
pub struct StatusSummary {
    pub staged: usize,
    pub unstaged: usize,
    pub untracked: usize,
}

#[derive(Debug, Clone)]
pub struct StatusEntry {
    pub staged: bool,
    pub unstaged: bool,
    pub conflict: bool,
    pub path: String,
}

/// Parse `git status --porcelain=v2` output into a summary.
pub fn parse_status(raw: &str) -> Result<StatusSummary> {
    let mut summary = StatusSummary::default();
    for line in raw.lines() {
        if line.starts_with("1 ") {
            // Format: "1 XY ..."
            let bytes = line.as_bytes();
            if bytes.len() >= 4 {
                let x = bytes[2] as char;
                let y = bytes[3] as char;
                if x != '.' {
                    summary.staged += 1;
                }
                if y != '.' {
                    summary.unstaged += 1;
                }
            }
        } else if line.starts_with("? ") {
            summary.untracked += 1;
        }
    }
    Ok(summary)
}

/// Parse `git status --porcelain=v2` into simple entries (path + staged/unstaged flags).
pub fn parse_status_entries(raw: &str) -> Vec<StatusEntry> {
    let mut entries = Vec::new();
    for line in raw.lines() {
        if line.starts_with("1 ") {
            // Format: "1 XY ... <path>"
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() >= 4 {
                let x = parts[1].chars().nth(0).unwrap_or('.');
                let y = parts[1].chars().nth(1).unwrap_or('.');
                let path = parts.last().unwrap_or(&"").to_string();
                let conflict = matches!(
                    (x, y),
                    ('U', _)
                        | (_, 'U')
                        | ('A', 'A')
                        | ('D', 'D')
                        | ('A', 'D')
                        | ('D', 'A')
                );
                entries.push(StatusEntry {
                    staged: x != '.',
                    unstaged: y != '.',
                    conflict,
                    path,
                });
            }
        } else if line.starts_with("? ") {
            let path = line.trim_start_matches("? ").to_string();
            entries.push(StatusEntry {
                staged: false,
                unstaged: false,
                conflict: false,
                path,
            });
        }
    }
    entries
}