//! Utility functions for git operations.

use std::{path::Path, process::Command};

use clingwrap::runner::{CommandError, CommandRunner};

/// Is a directory a git checkout?
pub fn is_git(dirname: &Path) -> bool {
    dirname.exists() && git_head(dirname).is_ok()
}

/// Return commit id for HEAD in a git checkout.
pub fn git_head(dirname: &Path) -> Result<String, GitError> {
    let stdout = git(&["rev-parse", "HEAD"], dirname)?;
    let head = stdout.trim().into();
    Ok(head)
}

/// Is the git checkout clean?
pub fn git_is_clean(dirname: &Path) -> bool {
    if let Ok(stdout) = git(&["status", "--short"], dirname) {
        stdout.is_empty()
    } else {
        false // some error happened, assume checkout is not clean
    }
}

// Run git command in a directory; if successful, return stdout as text.
fn git(args: &[&str], dirname: &Path) -> Result<String, GitError> {
    let mut cmd = Command::new("git");
    cmd.args(args).current_dir(dirname);

    let mut runner = CommandRunner::new(cmd);
    runner.capture_stdout();
    runner.capture_stderr();
    runner
        .execute()
        .map_err(GitError::Execute)
        .map(|output| String::from_utf8_lossy(&output.stdout).into())
}

/// Possible errors from git operations.
#[derive(Debug, thiserror::Error)]
pub enum GitError {
    /// Can't run git.
    #[error("failed to run git")]
    Execute(#[source] CommandError),
}
