Skip to main content

yui/
git.rs

1//! Lightweight git status check via shell-out.
2//!
3//! `apply` uses this to decide whether to escalate `AutoAbsorb` to
4//! `NeedsConfirm` when `[absorb] require_clean_git = true` — pulling
5//! target-side edits into a dirty source repo would mix yui's writes
6//! with the user's in-progress changes in a single commit.
7//!
8//! No git crate dependency: shells out to the `git` CLI. If `git` isn't
9//! installed or the directory isn't a repo, callers receive a clear
10//! `Error::Git` and can decide how lenient to be.
11
12use std::process::Command;
13
14use camino::Utf8Path;
15
16use crate::{Error, Result};
17
18/// Returns `true` when the working tree at `repo` has no uncommitted
19/// changes and no untracked files (i.e. `git status --porcelain` is
20/// empty). `Err(Error::Git(_))` if `git` isn't on `$PATH` or `repo`
21/// isn't a repository.
22pub fn is_clean(repo: &Utf8Path) -> Result<bool> {
23    let output = Command::new("git")
24        .arg("-C")
25        .arg(repo.as_str())
26        .arg("status")
27        .arg("--porcelain")
28        .output()
29        .map_err(|e| Error::Git(format!("invoking `git`: {e}")))?;
30    if !output.status.success() {
31        let stderr = String::from_utf8_lossy(&output.stderr);
32        return Err(Error::Git(format!(
33            "git status failed at {repo}: {}",
34            stderr.trim()
35        )));
36    }
37    Ok(output.stdout.is_empty())
38}