eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! Staging operations: git add, restore, diff.

use anyhow::{bail, Result};
use std::process::Command;
use std::path::Path;
use super::GitCli;

impl GitCli {
    /// Stage a file via `git add <file>`.
    pub fn stage_file(&self, repo_path: &str, file: &str) -> Result<()> {
        let output = Command::new("git")
            .args(["-C", repo_path, "add", "--", file])
            .output()?;

        if !output.status.success() {
            bail!(
                "git add failed (exit {}): {}",
                output.status,
                String::from_utf8_lossy(&output.stderr)
            );
        }

        Ok(())
    }

    /// Unstage a file via `git restore --staged <file>`.
    pub fn unstage_file(&self, repo_path: &str, file: &str) -> Result<()> {
        let output = Command::new("git")
            .args(["-C", repo_path, "restore", "--staged", "--", file])
            .output()?;

        if !output.status.success() {
            bail!(
                "git restore --staged failed (exit {}): {}",
                output.status,
                String::from_utf8_lossy(&output.stderr)
            );
        }

        Ok(())
    }

    /// Show diff for a single path with configurable context.
    pub fn diff(
        &self,
        repo_path: &str,
        path: &str,
        staged: bool,
        context: usize,
    ) -> Result<String> {
        let status_output = Command::new("git")
            .args(["-C", repo_path, "status", "--porcelain=v2", "--", path])
            .output()?;
        
        let is_untracked = if status_output.status.success() {
            String::from_utf8_lossy(&status_output.stdout).trim().starts_with("? ")
        } else {
            false
        };
        
        if is_untracked {
            let file_path = Path::new(repo_path).join(path);
            if !file_path.exists() {
                return Ok(String::new());
            }
            
            let ctx = format!("--unified={}", context);
            let output = Command::new("git")
                .args(["-C", repo_path, "diff", "--no-index", &ctx])
                .arg("/dev/null")
                .arg(&file_path)
                .output()?;
            
            if output.status.code() == Some(1) || output.status.success() {
                Ok(String::from_utf8_lossy(&output.stdout).into_owned())
            } else {
                bail!("git diff --no-index failed: {}", String::from_utf8_lossy(&output.stderr));
            }
        } else {
            let mut cmd = Command::new("git");
            cmd.args(["-C", repo_path, "diff"]);
            if staged {
                cmd.arg("--cached");
            }
            cmd.arg(format!("--unified={}", context));
            cmd.arg("--").arg(path);

            let output = cmd.output()?;
            if !output.status.success() {
                bail!("git diff failed: {}", String::from_utf8_lossy(&output.stderr));
            }

            Ok(String::from_utf8_lossy(&output.stdout).into_owned())
        }
    }
}