repopilot 0.11.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use crate::receipt::model::ReceiptGit;
use std::path::Path;
use std::process::Command;

pub fn collect_git_receipt(root: &Path) -> ReceiptGit {
    let git_root = if root.is_file() {
        root.parent().unwrap_or(root)
    } else {
        root
    };

    let is_git_repo = git_output(git_root, &["rev-parse", "--is-inside-work-tree"])
        .map(|value| value == "true")
        .unwrap_or(false);

    if !is_git_repo {
        return ReceiptGit {
            is_git_repo: false,
            branch: None,
            commit: None,
            dirty: false,
        };
    }

    let branch = git_output(git_root, &["rev-parse", "--abbrev-ref", "HEAD"])
        .filter(|branch| branch != "HEAD");

    let commit = git_output(git_root, &["rev-parse", "HEAD"]);

    let dirty = git_output_allow_empty(git_root, &["status", "--porcelain"])
        .map(|status| !status.trim().is_empty())
        .unwrap_or(false);

    ReceiptGit {
        is_git_repo,
        branch,
        commit,
        dirty,
    }
}

fn git_output(root: &Path, args: &[&str]) -> Option<String> {
    let output = Command::new("git")
        .current_dir(root)
        .args(args)
        .output()
        .ok()?;

    if !output.status.success() {
        return None;
    }

    let value = String::from_utf8(output.stdout).ok()?;
    let value = value.trim();

    if value.is_empty() {
        None
    } else {
        Some(value.to_string())
    }
}

fn git_output_allow_empty(root: &Path, args: &[&str]) -> Option<String> {
    let output = Command::new("git")
        .current_dir(root)
        .args(args)
        .output()
        .ok()?;

    if !output.status.success() {
        return None;
    }

    String::from_utf8(output.stdout).ok()
}