Skip to main content

lean_ctx/core/
git_util.rs

1//! Minimal, dependency-free git shell-outs shared by the context-artifact and
2//! analysis tools (`ctx_impact`, `ctx_architecture`, `context_artifacts`).
3//!
4//! These intentionally stay tiny and best-effort: a missing or failing git
5//! never aborts a tool, it just yields `false`/`None` so callers degrade
6//! gracefully.
7
8use std::path::Path;
9use std::process::{Command, Stdio};
10
11/// Returns `true` when the working tree at `project_root` has uncommitted
12/// changes. Any git failure (not a repo, git absent) reports `false`.
13pub(crate) fn git_dirty(project_root: &Path) -> bool {
14    let out = Command::new("git")
15        .args(["status", "--porcelain"])
16        .current_dir(project_root)
17        .stdout(Stdio::piped())
18        .stderr(Stdio::null())
19        .output();
20    match out {
21        Ok(o) if o.status.success() => !o.stdout.is_empty(),
22        _ => false,
23    }
24}
25
26/// Runs `git <args>` in `project_root` and returns trimmed stdout, or `None`
27/// on non-zero exit, non-UTF-8 output, empty output, or spawn failure.
28pub(crate) fn git_out(project_root: &Path, args: &[&str]) -> Option<String> {
29    let out = Command::new("git")
30        .args(args)
31        .current_dir(project_root)
32        .stdout(Stdio::piped())
33        .stderr(Stdio::null())
34        .output()
35        .ok()?;
36    if !out.status.success() {
37        return None;
38    }
39    let s = String::from_utf8(out.stdout).ok()?;
40    let s = s.trim().to_string();
41    if s.is_empty() { None } else { Some(s) }
42}