use assert_cmd::prelude::*;
use prview::git::git_cmd;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
const GIT_LEAK_VARS: &[&str] = &[
"GIT_DIR",
"GIT_WORK_TREE",
"GIT_INDEX_FILE",
"GIT_OBJECT_DIRECTORY",
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_COMMON_DIR",
];
fn run_git(repo: &Path, args: &[&str]) {
let status = git_cmd()
.args(args)
.current_dir(repo)
.status()
.expect("failed to run git");
assert!(status.success(), "git {:?} failed", args);
}
fn make_repo(branch: &str, content: &str) -> (TempDir, String) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path();
run_git(path, &["init", "-b", branch]);
run_git(path, &["config", "user.email", "test@test.com"]);
run_git(path, &["config", "user.name", "Test"]);
std::fs::write(path.join("file.txt"), content).expect("write");
run_git(path, &["add", "."]);
run_git(path, &["commit", "-m", content]);
let out = git_cmd()
.args(["rev-parse", "--short", "HEAD"])
.current_dir(path)
.output()
.expect("rev-parse");
let sha = String::from_utf8_lossy(&out.stdout).trim().to_string();
(dir, sha)
}
#[test]
fn git_cmd_strips_all_leak_vars() {
let cmd = git_cmd();
let removed: Vec<&str> = cmd
.get_envs()
.filter(|(_, v)| v.is_none())
.map(|(k, _)| k.to_str().unwrap_or(""))
.collect();
for var in GIT_LEAK_VARS {
assert!(
removed.contains(var),
"git_cmd() must env_remove {var}; removed = {removed:?}"
);
}
}
#[test]
fn poisoned_git_env_does_not_leak_into_prview() {
let (victim, victim_sha) = make_repo("victim-branch", "victim\n");
let (work, work_sha) = make_repo("work-branch", "work\n");
assert_ne!(victim_sha, work_sha, "fixtures must differ");
let victim_git_dir = victim.path().join(".git");
let assert = Command::new(assert_cmd::cargo::cargo_bin!("prview"))
.current_dir(work.path())
.args(["state", "--json", "--fast"])
.env("GIT_DIR", &victim_git_dir)
.env("GIT_WORK_TREE", victim.path())
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).expect("utf8");
let json: serde_json::Value =
serde_json::from_str(stdout.trim()).expect("state --json should emit JSON");
let reported = json["commit"].as_str().unwrap_or("");
assert_eq!(
reported, work_sha,
"prview must report the work repo's HEAD, not the poisoned victim's ({victim_sha})"
);
assert_eq!(json["branch"].as_str().unwrap_or(""), "work-branch");
let after = git_cmd()
.args(["rev-parse", "--short", "HEAD"])
.current_dir(victim.path())
.output()
.expect("victim rev-parse");
assert_eq!(
String::from_utf8_lossy(&after.stdout).trim(),
victim_sha,
"victim repo HEAD must not move"
);
}