use std::cell::{Cell, RefCell};
use std::ffi::{OsStr, OsString};
use std::process::Command;
use std::sync::{Mutex, MutexGuard, OnceLock};
fn process_env_mutex() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
thread_local! {
static PROCESS_ENV_LOCK_DEPTH: Cell<usize> = const { Cell::new(0) };
static PROCESS_ENV_LOCK_GUARD: RefCell<Option<MutexGuard<'static, ()>>> = const { RefCell::new(None) };
}
pub(crate) struct ProcessEnvLockGuard;
impl Drop for ProcessEnvLockGuard {
fn drop(&mut self) {
PROCESS_ENV_LOCK_DEPTH.with(|depth| {
let current = depth.get();
debug_assert!(current > 0, "process env lock depth underflow");
let next = current.saturating_sub(1);
depth.set(next);
if next == 0 {
PROCESS_ENV_LOCK_GUARD.with(|slot| {
slot.borrow_mut().take();
});
}
});
}
}
pub(crate) fn process_env_lock() -> ProcessEnvLockGuard {
PROCESS_ENV_LOCK_DEPTH.with(|depth| {
if depth.get() == 0 {
let guard = process_env_mutex()
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
PROCESS_ENV_LOCK_GUARD.with(|slot| {
*slot.borrow_mut() = Some(guard);
});
}
depth.set(depth.get() + 1);
});
ProcessEnvLockGuard
}
struct ScopedEnvVar {
key: &'static str,
previous: Option<OsString>,
}
impl ScopedEnvVar {
fn set(key: &'static str, value: &'static OsStr) -> Self {
let previous = std::env::var_os(key);
unsafe { std::env::set_var(key, value) };
Self { key, previous }
}
}
impl Drop for ScopedEnvVar {
fn drop(&mut self) {
if let Some(previous) = self.previous.take() {
unsafe { std::env::set_var(self.key, previous) };
} else {
unsafe { std::env::remove_var(self.key) };
}
}
}
#[cfg(windows)]
const HERMETIC_GIT_CONFIG_PATH: &str = "NUL";
#[cfg(not(windows))]
const HERMETIC_GIT_CONFIG_PATH: &str = "/dev/null";
#[allow(dead_code)]
pub(crate) fn hermetic_git_env() -> [(&'static str, &'static OsStr); 2] {
[
("GIT_CONFIG_GLOBAL", OsStr::new(HERMETIC_GIT_CONFIG_PATH)),
("GIT_CONFIG_SYSTEM", OsStr::new(HERMETIC_GIT_CONFIG_PATH)),
]
}
#[allow(dead_code)]
pub(crate) fn apply_hermetic_git_env(command: &mut Command) -> &mut Command {
command.envs(hermetic_git_env())
}
#[allow(dead_code)]
pub(crate) struct HermeticGitEnvGuard {
_lock: ProcessEnvLockGuard,
_global: ScopedEnvVar,
_system: ScopedEnvVar,
}
#[allow(dead_code)]
pub(crate) fn hermetic_git_env_guard() -> HermeticGitEnvGuard {
let lock = process_env_lock();
let [(global_key, global_value), (system_key, system_value)] = hermetic_git_env();
HermeticGitEnvGuard {
_lock: lock,
_global: ScopedEnvVar::set(global_key, global_value),
_system: ScopedEnvVar::set(system_key, system_value),
}
}