Skip to main content

tracel_xtask/utils/
git.rs

1use std::{
2    path::PathBuf,
3    process::{Command, Stdio},
4};
5
6use anyhow::Context;
7
8pub fn git_repo_root_or_cwd() -> anyhow::Result<PathBuf> {
9    let out = Command::new("git")
10        .arg("rev-parse")
11        .arg("--show-toplevel")
12        .stdout(Stdio::piped())
13        .stderr(Stdio::null())
14        .output();
15
16    match out {
17        Ok(o) if o.status.success() => {
18            let s = String::from_utf8(o.stdout).context("utf8 git rev-parse output")?;
19            Ok(PathBuf::from(s.trim()))
20        }
21        _ => std::env::current_dir().context("getting current directory"),
22    }
23}
24
25/// Returns the current commit hash of `HEAD`.
26pub fn git_current_commit_hash(len: Option<usize>) -> anyhow::Result<String> {
27    let cwd = git_repo_root_or_cwd()?;
28    let out = Command::new("git")
29        .arg("rev-parse")
30        .arg("--verify")
31        .arg("HEAD")
32        .current_dir(&cwd)
33        .stdout(Stdio::piped())
34        .stderr(Stdio::piped())
35        .output()
36        .context("running `git rev-parse --verify HEAD`")?;
37
38    if !out.status.success() {
39        anyhow::bail!(
40            "git rev-parse failed: {}",
41            String::from_utf8_lossy(&out.stderr).trim()
42        );
43    }
44
45    let s = String::from_utf8(out.stdout).context("utf8 git rev-parse output")?;
46    let sha = s.trim().to_string();
47    if sha.len() != 40 || !sha.chars().all(|c| c.is_ascii_hexdigit()) {
48        anyhow::bail!("unexpected commit hash format: {sha}");
49    }
50
51    let max_len = 40;
52    let final_len = len.map_or(max_len, |n| n.min(max_len));
53
54    Ok(sha[..final_len].to_string())
55}
56
57/// Return true if the current repository is dirty
58pub fn git_is_repo_dirty() -> anyhow::Result<bool> {
59    let cwd = git_repo_root_or_cwd()?;
60    let out = Command::new("git")
61        .arg("status")
62        .arg("--porcelain") // stable machine-readable output
63        .current_dir(&cwd)
64        .stdout(Stdio::piped())
65        .stderr(Stdio::piped())
66        .output()
67        .context("running `git status --porcelain`")?;
68
69    if !out.status.success() {
70        anyhow::bail!(
71            "git status failed: {}",
72            String::from_utf8_lossy(&out.stderr).trim()
73        );
74    }
75
76    let s = String::from_utf8(out.stdout).context("utf8 git status output")?;
77    Ok(!s.trim().is_empty())
78}