fallow_engine/git_env.rs
1//! Git process environment helpers owned by the engine boundary.
2
3use std::path::Path;
4use std::process::{Command, Stdio};
5
6/// Environment variables that describe an enclosing git operation's repository
7/// state and should not leak into fallow-owned git subprocesses.
8pub const AMBIENT_GIT_ENV_VARS: &[&str] = &[
9 "GIT_DIR",
10 "GIT_WORK_TREE",
11 "GIT_INDEX_FILE",
12 "GIT_OBJECT_DIRECTORY",
13 "GIT_COMMON_DIR",
14 "GIT_PREFIX",
15];
16
17/// Strip ambient git repository-state environment variables from a `Command`.
18///
19/// Returns the `Command` for fluent chaining alongside `.args()` and
20/// `.current_dir()`.
21pub fn clear_ambient_git_env(cmd: &mut Command) -> &mut Command {
22 for var in AMBIENT_GIT_ENV_VARS {
23 cmd.env_remove(var);
24 }
25 cmd
26}
27
28/// Build a `git` command that runs in `cwd`.
29///
30/// Every Fallow-owned Git process starts here:
31/// - The ambient repository-state environment is cleared.
32/// - Stdin is closed. Long-lived embedders keep protocol stdin open, which Git
33/// for Windows can inherit and hold.
34/// - Inside a deregistered base snapshot, which holds an untrusted commit, the
35/// command gets a `GIT_DIR` that does not exist, so Git finds no repository.
36/// - `safe.bareRepository=explicit` stops Git from using a directory shaped
37/// as a bare repository that it finds on its own. Checkouts, their
38/// subdirectories and linked worktrees are not affected.
39#[expect(
40 clippy::disallowed_methods,
41 reason = "engine-owned git spawn wrapper clears ambient git env before every git subprocess"
42)]
43pub fn git_command(cwd: &Path) -> Command {
44 let mut command = Command::new("git");
45 clear_ambient_git_env(&mut command);
46 crate::base_worktree_registration::confine_git_to_host(&mut command, cwd);
47 command
48 .stdin(Stdio::null())
49 .args(["-c", "safe.bareRepository=explicit"])
50 .current_dir(cwd);
51 command
52}