ferrflow 7.13.3

Universal semantic versioning for monorepos and classic repos
Documentation
use anyhow::{Context, Result, anyhow};
use std::path::Path;
use std::process::Command;

use super::auth::scrub_trace_env;

pub(super) fn run_git(workdir: &Path, args: &[&str]) -> Result<String> {
    run_git_with_env(workdir, args, &[])
}

pub(super) fn run_git_with_env(
    workdir: &Path,
    args: &[&str],
    extra_env: &[(&str, &str)],
) -> Result<String> {
    let mut cmd = Command::new("git");
    cmd.current_dir(workdir);
    scrub_trace_env(&mut cmd);
    for (k, v) in extra_env {
        cmd.env(k, v);
    }
    cmd.args(args);
    let output = cmd
        .output()
        .with_context(|| format!("spawn `git {}` failed (is git in PATH?)", args.join(" ")))?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let combined = scrub_token_patterns(&format!("{stdout}{stderr}"))
            .trim()
            .to_string();
        return Err(anyhow!("git {} failed: {}", args.join(" "), combined));
    }
    Ok(String::from_utf8_lossy(&output.stdout).into_owned())
}

fn scrub_token_patterns(s: &str) -> String {
    use std::sync::OnceLock;
    static RE: OnceLock<regex::Regex> = OnceLock::new();
    let re = RE.get_or_init(|| {
        regex::Regex::new(
            r"(?i)(authorization:\s*\S+|(?:ghs|ghp|gho|ghu|github_pat|glpat)_[A-Za-z0-9_]{20,})",
        )
        .expect("scrub regex must compile")
    });
    re.replace_all(s, "[REDACTED]").into_owned()
}