laziergit 1.0.0

an even lazier git implementation than lazygit
use color_eyre::eyre::{Result, bail};
use std::process::Command;

fn run(args: &[&str]) -> Result<Vec<u8>> {
    let out = Command::new("git").args(args).output()?;
    if !out.status.success() {
        bail!("{}", String::from_utf8_lossy(&out.stderr).trim());
    }
    Ok(out.stdout)
}

fn capture(args: &[&str]) -> String {
    Command::new("git")
        .args(args)
        .output()
        .map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
        .unwrap_or_default()
}

pub fn status_raw() -> Result<Vec<u8>> {
    run(&["status", "--porcelain=v2", "--branch", "-z", "-uall"])
}

pub fn diff(path: &str, staged: bool) -> String {
    if staged {
        capture(&["diff", "--no-color", "--cached", "--", path])
    } else {
        capture(&["diff", "--no-color", "--", path])
    }
}

pub fn diff_untracked(path: &str) -> String {
    capture(&["diff", "--no-color", "--no-index", "--", "/dev/null", path])
}

pub fn stage(pathspec: &str) -> Result<()> {
    run(&["add", "--", pathspec]).map(drop)
}

pub fn unstage(pathspec: &str) -> Result<()> {
    if run(&["restore", "--staged", "--", pathspec]).is_ok() {
        return Ok(());
    }
    run(&["rm", "--cached", "-r", "-q", "--", pathspec]).map(drop)
}

pub fn commit(message: &str) -> Result<()> {
    run(&["commit", "-m", message]).map(drop)
}

pub fn checkout(name: &str) -> Result<()> {
    if run(&["checkout", name]).is_ok() {
        return Ok(());
    }
    run(&["checkout", "-b", name]).map(drop)
}

pub fn reset_hard_clean() -> Result<()> {
    run(&["reset", "--hard"])?;
    run(&["clean", "-fd"])?;
    Ok(())
}

pub fn pull() -> Result<String> {
    remote(&["pull"])
}

pub fn push() -> Result<String> {
    remote(&["push"])
}

fn remote(args: &[&str]) -> Result<String> {
    let out = Command::new("git").args(args).output()?;
    let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
    text.push_str(&String::from_utf8_lossy(&out.stderr));
    let last = text
        .lines()
        .rev()
        .find(|l| !l.trim().is_empty())
        .unwrap_or("done")
        .trim()
        .to_string();
    if out.status.success() {
        Ok(last)
    } else {
        bail!("{last}");
    }
}