tsafe-cli 1.0.21

tsafe CLI — local secret and credential manager (replaces .env files)
Documentation
//! Git PAT-injection command handler.
//!
//! Implements `tsafe git` — runs any git subcommand with the vault PAT injected
//! via `GIT_CONFIG_*` environment variables, avoiding URL mutation or temp files.

use anyhow::{Context, Result};
use colored::Colorize;
use tsafe_core::audit::AuditEntry;

use crate::helpers::*;

/// Run a git command with the vault PAT injected via http.extraHeader.
///
/// Looks for `ADO_PAT` in the vault (or the key named by `TSAFE_GIT_PAT_KEY`).
/// Encodes it as `Authorization: Basic base64(:PAT)` and injects it via git's
/// GIT_CONFIG_* env-var mechanism — no URL mutation, no temp files.
pub(crate) fn cmd_git(profile: &str, args: Vec<String>) -> Result<()> {
    use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
    use std::process::Command;

    if args.is_empty() {
        anyhow::bail!("no git subcommand given. Usage: tsafe git <subcommand> [args...]");
    }

    let vault = open_vault(profile)?;

    // Determine which vault key holds the PAT (default: ADO_PAT).
    let pat_key = std::env::var("TSAFE_GIT_PAT_KEY").unwrap_or_else(|_| "ADO_PAT".to_string());
    let pat = vault.get(&pat_key).ok().map(|z| z.to_string());

    // Drop vault (releases lock) before spawning — process::exit bypasses Drop.
    drop(vault);

    let mut cmd = Command::new("git");
    cmd.args(&args);

    if let Some(ref p) = pat {
        // git accepts http.extraHeader via environment: GIT_CONFIG_COUNT + GIT_CONFIG_KEY_N + GIT_CONFIG_VALUE_N
        let encoded = B64.encode(format!(":{p}"));
        cmd.env("GIT_CONFIG_COUNT", "1");
        cmd.env("GIT_CONFIG_KEY_0", "http.extraHeader");
        cmd.env(
            "GIT_CONFIG_VALUE_0",
            format!("Authorization: Basic {encoded}"),
        );
    } else {
        eprintln!(
            "{} Vault key '{}' not found — running git without injected credentials.",
            "i".blue(),
            pat_key
        );
    }

    audit(profile)
        .append(&AuditEntry::success(profile, "git", None))
        .ok();

    let status = cmd.status().context("failed to spawn git")?;
    std::process::exit(status.code().unwrap_or(1));
}