mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
Merge apply filter neutralization into staging guard
This commit is contained in:
@@ -13,7 +13,30 @@ use crate::git_config::parse_effective_config;
|
||||
use crate::git_config::parse_effective_config_with_origins;
|
||||
|
||||
pub(crate) const DISABLED_HOOKS_PATH: &str = if cfg!(windows) { "NUL" } else { "/dev/null" };
|
||||
pub(crate) const EXECUTABLE_FILTER_CONFIG_PATTERN: &str = r"^filter\..*\.(clean|smudge|process)$";
|
||||
pub(crate) const EXECUTABLE_FILTER_CONFIG_PATTERN: &str =
|
||||
r"^filter\..*\.(clean|smudge|process|required)$";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum FilterAttributeValue {
|
||||
Driver(String),
|
||||
AmbiguousSentinel(String),
|
||||
}
|
||||
|
||||
pub(crate) struct GitFilterNeutralization {
|
||||
git_config_args: Vec<String>,
|
||||
_config_dir: Option<tempfile::TempDir>,
|
||||
filter_config: BTreeMap<String, GitConfigEntry>,
|
||||
}
|
||||
|
||||
impl GitFilterNeutralization {
|
||||
pub(crate) fn git_config_args(&self) -> &[String] {
|
||||
&self.git_config_args
|
||||
}
|
||||
|
||||
pub(crate) fn filter_value(&self, driver: &str, name: &str) -> Option<&str> {
|
||||
effective_filter_value(&self.filter_config, driver, name)
|
||||
}
|
||||
}
|
||||
|
||||
const ISOLATED_GIT_ENVIRONMENT: [&str; 11] = [
|
||||
"GIT_DIR",
|
||||
@@ -47,7 +70,7 @@ pub(crate) fn ensure_no_selected_executable_git_filters(
|
||||
cwd: &Path,
|
||||
paths: &[String],
|
||||
git_config_args: &[String],
|
||||
) -> io::Result<()> {
|
||||
) -> io::Result<GitFilterNeutralization> {
|
||||
ensure_no_selected_executable_git_filters_for(
|
||||
git,
|
||||
cwd,
|
||||
@@ -62,7 +85,7 @@ pub(crate) fn ensure_no_selected_git_add_filters(
|
||||
cwd: &Path,
|
||||
paths: &[String],
|
||||
git_config_args: &[String],
|
||||
) -> io::Result<()> {
|
||||
) -> io::Result<GitFilterNeutralization> {
|
||||
ensure_no_selected_executable_git_filters_for(
|
||||
git,
|
||||
cwd,
|
||||
@@ -78,27 +101,114 @@ fn ensure_no_selected_executable_git_filters_for(
|
||||
paths: &[String],
|
||||
git_config_args: &[String],
|
||||
execution: FilterExecution,
|
||||
) -> io::Result<()> {
|
||||
) -> io::Result<GitFilterNeutralization> {
|
||||
let entries = read_filter_config(git, cwd, git_config_args)?;
|
||||
if !entries.values().any(|entry| !entry.value.is_empty()) {
|
||||
return Ok(());
|
||||
let neutralized_drivers = executable_filter_drivers_for(&entries, execution)?;
|
||||
let target_drivers = match execution {
|
||||
FilterExecution::AnyWorktreeOperation => neutralized_drivers.clone(),
|
||||
FilterExecution::GitAdd => executable_filter_drivers(&entries)?,
|
||||
};
|
||||
if target_drivers.is_empty() {
|
||||
return Ok(GitFilterNeutralization {
|
||||
git_config_args: Vec::new(),
|
||||
_config_dir: None,
|
||||
filter_config: entries,
|
||||
});
|
||||
}
|
||||
let paths = paths
|
||||
.iter()
|
||||
.map(|path| path.as_bytes().to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
let attributes = read_filter_attributes(git, cwd, &paths, git_config_args)?;
|
||||
if let Some((driver, path)) = selected_executable_filter_for(&entries, &attributes, execution)?
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
format!(
|
||||
"refusing to run an internal Git worktree operation with executable filter {driver:?} selected for {}",
|
||||
String::from_utf8_lossy(&path)
|
||||
),
|
||||
));
|
||||
let attributes = read_filter_attributes(git, cwd, &paths, git_config_args, &target_drivers)?;
|
||||
for (path, driver) in &attributes {
|
||||
if !target_drivers.contains(driver) {
|
||||
continue;
|
||||
}
|
||||
let refused = neutralized_drivers.contains(driver)
|
||||
|| matches!(execution, FilterExecution::GitAdd)
|
||||
&& git_filter_required(git, cwd, &entries, driver)?;
|
||||
if refused {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
format!(
|
||||
"refusing to run an internal Git worktree operation with executable filter {driver:?} selected for {}",
|
||||
String::from_utf8_lossy(path)
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
if neutralized_drivers.is_empty() {
|
||||
return Ok(GitFilterNeutralization {
|
||||
git_config_args: Vec::new(),
|
||||
_config_dir: None,
|
||||
filter_config: entries,
|
||||
});
|
||||
}
|
||||
executable_filter_guard(git, cwd, entries, &neutralized_drivers)
|
||||
}
|
||||
|
||||
fn executable_filter_guard(
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
filter_config: BTreeMap<String, GitConfigEntry>,
|
||||
executable_drivers: &BTreeSet<String>,
|
||||
) -> io::Result<GitFilterNeutralization> {
|
||||
let config_dir = tempfile::tempdir()?;
|
||||
let config_path = config_dir.path().join("filter-neutralization.gitconfig");
|
||||
std::fs::write(&config_path, [])?;
|
||||
let guard = GitFilterNeutralization {
|
||||
git_config_args: vec![
|
||||
"-c".to_string(),
|
||||
format!(
|
||||
"include.path={}",
|
||||
config_path
|
||||
.to_str()
|
||||
.ok_or_else(|| invalid_filter_output("non-UTF-8 filter guard path"))?
|
||||
),
|
||||
],
|
||||
_config_dir: Some(config_dir),
|
||||
filter_config,
|
||||
};
|
||||
for driver in executable_drivers {
|
||||
debug_assert!(["clean", "smudge", "process"].into_iter().any(|name| {
|
||||
guard
|
||||
.filter_value(driver, name)
|
||||
.is_some_and(|value| !value.is_empty())
|
||||
}));
|
||||
for command in ["clean", "smudge", "process"] {
|
||||
guard.write_config_value(git, cwd, &config_path, driver, command, "")?;
|
||||
}
|
||||
guard.write_config_value(git, cwd, &config_path, driver, "required", "false")?;
|
||||
}
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
impl GitFilterNeutralization {
|
||||
fn write_config_value(
|
||||
&self,
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
config_path: &Path,
|
||||
driver: &str,
|
||||
name: &str,
|
||||
value: &str,
|
||||
) -> io::Result<()> {
|
||||
let mut command = git.command();
|
||||
command
|
||||
.args(["config", "--file"])
|
||||
.arg(config_path)
|
||||
.args(["--add", &format!("filter.{driver}.{name}"), value])
|
||||
.current_dir(cwd);
|
||||
let output = git.output(command)?;
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::other(format!(
|
||||
"failed to write Git filter neutralization for {driver:?} (status {}): {}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -192,6 +302,7 @@ fn read_filter_attributes(
|
||||
cwd: &Path,
|
||||
paths: &[Vec<u8>],
|
||||
git_config_args: &[String],
|
||||
executable_drivers: &BTreeSet<String>,
|
||||
) -> io::Result<BTreeMap<Vec<u8>, String>> {
|
||||
if paths.is_empty() {
|
||||
return Ok(BTreeMap::new());
|
||||
@@ -224,7 +335,131 @@ fn read_filter_attributes(
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
)));
|
||||
}
|
||||
parse_filter_attributes(&output.stdout, paths)
|
||||
let attributes = parse_filter_attributes(&output.stdout, paths)?;
|
||||
resolve_filter_attribute_sentinels(git, cwd, attributes, git_config_args, executable_drivers)
|
||||
}
|
||||
|
||||
fn resolve_filter_attribute_sentinels(
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
attributes: BTreeMap<Vec<u8>, FilterAttributeValue>,
|
||||
git_config_args: &[String],
|
||||
executable_drivers: &BTreeSet<String>,
|
||||
) -> io::Result<BTreeMap<Vec<u8>, String>> {
|
||||
let mut resolved = BTreeMap::new();
|
||||
for (path, attribute) in attributes {
|
||||
match attribute {
|
||||
FilterAttributeValue::Driver(driver) => {
|
||||
resolved.insert(path, driver);
|
||||
}
|
||||
FilterAttributeValue::AmbiguousSentinel(driver) => {
|
||||
if executable_drivers.contains(&driver)
|
||||
&& sentinel_spelling_selects_filter_driver(
|
||||
git,
|
||||
cwd,
|
||||
&path,
|
||||
&driver,
|
||||
git_config_args,
|
||||
)?
|
||||
{
|
||||
resolved.insert(path, driver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
/// `git check-attr` serializes both its three special states and literal
|
||||
/// driver names with the same `set`, `unset`, and `unspecified` strings. Ask
|
||||
/// Git to resolve the ambiguity with every command for that driver overridden
|
||||
/// to empty. A required literal driver fails while a special state succeeds.
|
||||
/// Retrying with the driver optional distinguishes that expected failure from
|
||||
/// an unrelated probe error. No filter process or shell is started.
|
||||
fn sentinel_spelling_selects_filter_driver(
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
path: &[u8],
|
||||
driver: &str,
|
||||
git_config_args: &[String],
|
||||
) -> io::Result<bool> {
|
||||
let required = run_sentinel_selection_probe(
|
||||
git,
|
||||
cwd,
|
||||
path,
|
||||
driver,
|
||||
git_config_args,
|
||||
/*required*/ true,
|
||||
)?;
|
||||
if required.status.success() {
|
||||
return Ok(false);
|
||||
}
|
||||
let optional = run_sentinel_selection_probe(
|
||||
git,
|
||||
cwd,
|
||||
path,
|
||||
driver,
|
||||
git_config_args,
|
||||
/*required*/ false,
|
||||
)?;
|
||||
if optional.status.success() {
|
||||
return Ok(true);
|
||||
}
|
||||
Err(io::Error::other(format!(
|
||||
"git filter attribute selection probe failed with required status {} and optional status {}: {}",
|
||||
required.status,
|
||||
optional.status,
|
||||
String::from_utf8_lossy(&optional.stderr).trim()
|
||||
)))
|
||||
}
|
||||
|
||||
fn run_sentinel_selection_probe(
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
path: &[u8],
|
||||
driver: &str,
|
||||
git_config_args: &[String],
|
||||
required: bool,
|
||||
) -> io::Result<std::process::Output> {
|
||||
let mut command = git.command();
|
||||
command
|
||||
.env("GIT_OPTIONAL_LOCKS", "0")
|
||||
.args(git_config_args)
|
||||
.args([
|
||||
"-c",
|
||||
&format!("core.hooksPath={DISABLED_HOOKS_PATH}"),
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
"-c",
|
||||
&format!("filter.{driver}.required={required}"),
|
||||
"-c",
|
||||
&format!("filter.{driver}.clean="),
|
||||
"-c",
|
||||
&format!("filter.{driver}.smudge="),
|
||||
"-c",
|
||||
&format!("filter.{driver}.process="),
|
||||
"hash-object",
|
||||
"--stdin",
|
||||
])
|
||||
.arg("--path")
|
||||
.arg(git_path_argument(path)?)
|
||||
.current_dir(cwd)
|
||||
.stdin(Stdio::null());
|
||||
git.output(command)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn git_path_argument(path: &[u8]) -> io::Result<std::ffi::OsString> {
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
|
||||
Ok(std::ffi::OsString::from_vec(path.to_vec()))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn git_path_argument(path: &[u8]) -> io::Result<std::ffi::OsString> {
|
||||
let path = std::str::from_utf8(path)
|
||||
.map_err(|_| invalid_filter_output("non-UTF-8 Git filter attribute path"))?;
|
||||
Ok(path.into())
|
||||
}
|
||||
|
||||
fn selected_executable_filter_for(
|
||||
@@ -232,23 +467,20 @@ fn selected_executable_filter_for(
|
||||
attributes: &BTreeMap<Vec<u8>, String>,
|
||||
execution: FilterExecution,
|
||||
) -> io::Result<Option<(String, Vec<u8>)>> {
|
||||
let mut executable_drivers = BTreeSet::new();
|
||||
for entry in entries.values() {
|
||||
let (driver, command) = filter_driver_and_command(&entry.key)?;
|
||||
let relevant = match execution {
|
||||
FilterExecution::AnyWorktreeOperation => true,
|
||||
FilterExecution::GitAdd => command != FilterCommand::Smudge,
|
||||
};
|
||||
if relevant && !entry.value.is_empty() {
|
||||
executable_drivers.insert(driver);
|
||||
}
|
||||
}
|
||||
let executable_drivers = executable_filter_drivers_for(entries, execution)?;
|
||||
Ok(selected_filter(&executable_drivers, attributes))
|
||||
}
|
||||
|
||||
fn selected_filter(
|
||||
drivers: &BTreeSet<String>,
|
||||
attributes: &BTreeMap<Vec<u8>, String>,
|
||||
) -> Option<(String, Vec<u8>)> {
|
||||
for (path, driver) in attributes {
|
||||
if executable_drivers.contains(driver) {
|
||||
return Ok(Some((driver.clone(), path.clone())));
|
||||
if drivers.contains(driver) {
|
||||
return Some((driver.clone(), path.clone()));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -259,7 +491,87 @@ fn selected_executable_filter(
|
||||
selected_executable_filter_for(entries, attributes, FilterExecution::AnyWorktreeOperation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn executable_filter_drivers(
|
||||
entries: &BTreeMap<String, GitConfigEntry>,
|
||||
) -> io::Result<BTreeSet<String>> {
|
||||
executable_filter_drivers_for(entries, FilterExecution::AnyWorktreeOperation)
|
||||
}
|
||||
|
||||
fn executable_filter_drivers_for(
|
||||
entries: &BTreeMap<String, GitConfigEntry>,
|
||||
execution: FilterExecution,
|
||||
) -> io::Result<BTreeSet<String>> {
|
||||
let mut executable_drivers = BTreeSet::new();
|
||||
for entry in entries.values() {
|
||||
if entry.key.ends_with(".required") {
|
||||
continue;
|
||||
}
|
||||
let (driver, command) = filter_driver_and_command(&entry.key)?;
|
||||
let relevant = match execution {
|
||||
FilterExecution::AnyWorktreeOperation => true,
|
||||
FilterExecution::GitAdd => command != FilterCommand::Smudge,
|
||||
};
|
||||
if relevant && !entry.value.is_empty() {
|
||||
executable_drivers.insert(driver);
|
||||
}
|
||||
}
|
||||
Ok(executable_drivers)
|
||||
}
|
||||
|
||||
fn effective_filter_value<'a>(
|
||||
entries: &'a BTreeMap<String, GitConfigEntry>,
|
||||
driver: &str,
|
||||
name: &str,
|
||||
) -> Option<&'a str> {
|
||||
entries
|
||||
.get(&format!("filter.{driver}.{name}"))
|
||||
.map(|entry| entry.value.as_str())
|
||||
}
|
||||
|
||||
fn git_filter_required(
|
||||
git: &GitRunner,
|
||||
cwd: &Path,
|
||||
entries: &BTreeMap<String, GitConfigEntry>,
|
||||
driver: &str,
|
||||
) -> io::Result<bool> {
|
||||
let Some(value) = effective_filter_value(entries, driver, "required") else {
|
||||
return Ok(false);
|
||||
};
|
||||
let config = format!("codex.required={value}");
|
||||
let mut command = git.command();
|
||||
command
|
||||
.env("GIT_OPTIONAL_LOCKS", "0")
|
||||
.args([
|
||||
"-c",
|
||||
&config,
|
||||
"config",
|
||||
"--type=bool",
|
||||
"--get",
|
||||
"codex.required",
|
||||
])
|
||||
.current_dir(cwd);
|
||||
let output = git.output(command)?;
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
format!(
|
||||
"refusing selected Git filter {driver:?} with malformed required value {value:?}: {}",
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
),
|
||||
));
|
||||
}
|
||||
match String::from_utf8_lossy(&output.stdout).trim() {
|
||||
"true" => Ok(true),
|
||||
"false" => Ok(false),
|
||||
normalized => Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
format!(
|
||||
"refusing selected Git filter {driver:?} with unexpected normalized required value {normalized:?}"
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_driver_name(key: &str) -> io::Result<String> {
|
||||
filter_driver_and_command(key).map(|(driver, _command)| driver)
|
||||
}
|
||||
@@ -279,7 +591,6 @@ fn filter_driver_and_command(key: &str) -> io::Result<(String, FilterCommand)> {
|
||||
.strip_suffix(suffix)
|
||||
.map(|driver| (driver, command))
|
||||
})
|
||||
.filter(|(driver, _command)| !driver.is_empty())
|
||||
.ok_or_else(|| invalid_filter_output("malformed filter config key"))?;
|
||||
Ok((driver.to_string(), command))
|
||||
}
|
||||
@@ -301,7 +612,7 @@ fn write_nul_paths(input: &mut std::fs::File, paths: &[Vec<u8>]) -> io::Result<(
|
||||
fn parse_filter_attributes(
|
||||
output: &[u8],
|
||||
expected_paths: &[Vec<u8>],
|
||||
) -> io::Result<BTreeMap<Vec<u8>, String>> {
|
||||
) -> io::Result<BTreeMap<Vec<u8>, FilterAttributeValue>> {
|
||||
let expected = expected_paths
|
||||
.iter()
|
||||
.map(Vec::as_slice)
|
||||
@@ -329,10 +640,13 @@ fn parse_filter_attributes(
|
||||
}
|
||||
let driver = std::str::from_utf8(record[2])
|
||||
.map_err(|_| invalid_filter_output("non-UTF-8 Git filter attribute value"))?;
|
||||
if attributes
|
||||
.insert(record[0].to_vec(), driver.to_string())
|
||||
.is_some()
|
||||
{
|
||||
let value = match driver {
|
||||
"set" | "unset" | "unspecified" => {
|
||||
FilterAttributeValue::AmbiguousSentinel(driver.to_string())
|
||||
}
|
||||
_ => FilterAttributeValue::Driver(driver.to_string()),
|
||||
};
|
||||
if attributes.insert(record[0].to_vec(), value).is_some() {
|
||||
return Err(invalid_filter_output(
|
||||
"duplicate Git filter attribute record",
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user