fix(git-utils): preserve lexical status authority

This commit is contained in:
Chris Bookholt
2026-07-02 08:45:49 -07:00
parent 932d5c3d38
commit 9e74f19d43
2 changed files with 77 additions and 1 deletions

View File

@@ -346,10 +346,13 @@ async fn try_get_has_changes_within_deadline(
cwd: &Path,
phase: &AtomicU8,
) -> Result<bool, GitReadError> {
// Preserve the caller's lexical spelling for authority discovery. A
// repository-controlled symlink prefix can disappear under
// canonicalization, but it must still constrain primary Git selection.
let requested_cwd = std::fs::canonicalize(cwd).map_err(|_| GitReadError::NotRepository {
path: cwd.to_path_buf(),
})?;
let git = GitRunner::for_cwd(&requested_cwd)?;
let git = GitRunner::for_cwd(cwd)?;
let mut config = Box::pin(prepare_status_config(&git, &requested_cwd)).await?;
phase.store(StatusReadPhase::Fsmonitor as u8, Ordering::Relaxed);
detect_status_fsmonitor(&mut config).await;

View File

@@ -746,6 +746,79 @@ async fn checked_has_changes_accepts_non_utf8_repository_root() {
assert_eq!(try_get_has_changes(&repo_path).await, Ok(false));
}
#[cfg(unix)]
#[tokio::test]
async fn checked_has_changes_preserves_lexical_repository_ancestry_for_git_selection() {
if std::env::var_os("CODEX_GIT_UTILS_SAFE_GIT_ENV_CHILD").is_none() {
use std::os::unix::fs::PermissionsExt;
let fixture = tempfile::tempdir().expect("fixture");
let outer = fixture.path().join("outer");
let physical_nested = fixture.path().join("physical-nested");
let lexical_nested = outer.join("nested");
let outer_bin = outer.join("bin");
std::fs::create_dir_all(&outer_bin).expect("create outer repository bin");
std::fs::create_dir_all(&physical_nested).expect("create nested repository");
run_git_async(&outer, &["init", "-q"]).await;
run_git_async(&physical_nested, &["init", "-q"]).await;
std::os::unix::fs::symlink(&physical_nested, &lexical_nested)
.expect("symlink nested repository");
let output = std::process::Command::new("/bin/sh")
.args(["-c", "command -v git"])
.output()
.expect("resolve Git executable");
assert!(output.status.success(), "resolve Git executable");
let real_git = PathBuf::from(
String::from_utf8(output.stdout)
.expect("Git path UTF-8")
.trim(),
);
let marker = fixture.path().join("outer-git-ran");
let outer_git = outer_bin.join("git");
std::fs::write(
&outer_git,
"#!/bin/sh\nprintf ran > \"$CODEX_GIT_UTILS_LEXICAL_GIT_MARKER\"\nexec \"$CODEX_GIT_UTILS_REAL_GIT\" \"$@\"\n",
)
.expect("write outer Git wrapper");
let mut permissions = std::fs::metadata(&outer_git)
.expect("outer Git metadata")
.permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(&outer_git, permissions).expect("make outer Git executable");
let search_path = std::env::join_paths([
outer_bin.as_path(),
real_git.parent().expect("Git executable directory"),
])
.expect("construct controlled PATH");
run_isolated_test(
"safe_git::tests::checked_has_changes_preserves_lexical_repository_ancestry_for_git_selection",
&[
("CODEX_GIT_UTILS_TARGET_REPO", lexical_nested.as_os_str()),
("CODEX_GIT_UTILS_LEXICAL_GIT_MARKER", marker.as_os_str()),
("CODEX_GIT_UTILS_REAL_GIT", real_git.as_os_str()),
("PATH", search_path.as_os_str()),
],
);
assert!(
!marker.exists(),
"Git from the lexical enclosing repository must not run"
);
return;
}
let lexical_nested =
PathBuf::from(std::env::var_os("CODEX_GIT_UTILS_TARGET_REPO").expect("target repository"));
assert_eq!(try_get_has_changes(&lexical_nested).await, Ok(false));
let marker =
PathBuf::from(std::env::var_os("CODEX_GIT_UTILS_LEXICAL_GIT_MARKER").expect("Git marker"));
assert!(
!marker.exists(),
"Git from the lexical enclosing repository must not run"
);
}
#[cfg(unix)]
#[tokio::test]
async fn root_probe_distinguishes_repository_command_failure() {