[codex] Migrate apply_patch to executor filesystem (#17027)

- Migrate apply-patch verification and application internals to use the
async `ExecutorFileSystem` abstraction from `exec-server`.
- Convert apply-patch `cwd` handling to `AbsolutePathBuf` through the
verifier/parser/handler boundary.

Doesn't change how the tool itself works.
This commit is contained in:
pakrym-oai
2026-04-07 14:20:22 -07:00
committed by GitHub
Unverified
parent d45513ce5a
commit e9702411ab
22 changed files with 817 additions and 334 deletions
+112 -78
View File
@@ -2,6 +2,8 @@ use std::collections::HashMap;
use std::path::Path;
use std::sync::LazyLock;
use codex_exec_server::ExecutorFileSystem;
use codex_utils_absolute_path::AbsolutePathBuf;
use tree_sitter::Parser;
use tree_sitter::Query;
use tree_sitter::QueryCursor;
@@ -129,7 +131,11 @@ pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch {
/// cwd must be an absolute path so that we can resolve relative paths in the
/// patch.
pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApplyPatchVerified {
pub async fn maybe_parse_apply_patch_verified(
argv: &[String],
cwd: &AbsolutePathBuf,
fs: &dyn ExecutorFileSystem,
) -> MaybeApplyPatchVerified {
// Detect a raw patch body passed directly as the command or as the body of a shell
// script. In these cases, report an explicit error rather than applying the patch.
if let [body] = argv
@@ -151,24 +157,20 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApp
}) => {
let effective_cwd = workdir
.as_ref()
.map(|dir| {
let path = Path::new(dir);
if path.is_absolute() {
path.to_path_buf()
} else {
cwd.join(path)
}
})
.unwrap_or_else(|| cwd.to_path_buf());
.map(|dir| cwd.join(Path::new(dir)))
.unwrap_or_else(|| cwd.clone());
let mut changes = HashMap::new();
for hunk in hunks {
let path = hunk.resolve_path(&effective_cwd);
match hunk {
Hunk::AddFile { contents, .. } => {
changes.insert(path, ApplyPatchFileChange::Add { content: contents });
changes.insert(
path.into_path_buf(),
ApplyPatchFileChange::Add { content: contents },
);
}
Hunk::DeleteFile { .. } => {
let content = match std::fs::read_to_string(&path) {
let content = match fs.read_file_text(&path).await {
Ok(content) => content,
Err(e) => {
return MaybeApplyPatchVerified::CorrectnessError(
@@ -179,7 +181,10 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApp
);
}
};
changes.insert(path, ApplyPatchFileChange::Delete { content });
changes.insert(
path.into_path_buf(),
ApplyPatchFileChange::Delete { content },
);
}
Hunk::UpdateFile {
move_path, chunks, ..
@@ -187,17 +192,17 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApp
let ApplyPatchFileUpdate {
unified_diff,
content: contents,
} = match unified_diff_from_chunks(&path, &chunks) {
} = match unified_diff_from_chunks(&path, &chunks, fs).await {
Ok(diff) => diff,
Err(e) => {
return MaybeApplyPatchVerified::CorrectnessError(e);
}
};
changes.insert(
path,
path.into_path_buf(),
ApplyPatchFileChange::Update {
unified_diff,
move_path: move_path.map(|p| effective_cwd.join(p)),
move_path: move_path.map(|p| effective_cwd.join(p).into_path_buf()),
new_content: contents,
},
);
@@ -371,7 +376,10 @@ fn extract_apply_patch_from_bash(
#[cfg(test)]
mod tests {
use super::*;
use crate::unified_diff_from_chunks;
use assert_matches::assert_matches;
use codex_exec_server::LOCAL_FS;
use codex_utils_absolute_path::test_support::PathExt;
use pretty_assertions::assert_eq;
use std::fs;
use std::path::PathBuf;
@@ -450,30 +458,40 @@ mod tests {
);
}
#[test]
fn test_implicit_patch_single_arg_is_error() {
#[tokio::test]
async fn test_implicit_patch_single_arg_is_error() {
let patch = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch".to_string();
let args = vec![patch];
let dir = tempdir().unwrap();
assert_matches!(
maybe_parse_apply_patch_verified(&args, dir.path()),
maybe_parse_apply_patch_verified(
&args,
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
LOCAL_FS.as_ref()
)
.await,
MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation)
);
}
#[test]
fn test_implicit_patch_bash_script_is_error() {
#[tokio::test]
async fn test_implicit_patch_bash_script_is_error() {
let script = "*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch";
let args = args_bash(script);
let dir = tempdir().unwrap();
assert_matches!(
maybe_parse_apply_patch_verified(&args, dir.path()),
maybe_parse_apply_patch_verified(
&args,
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
LOCAL_FS.as_ref()
)
.await,
MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation)
);
}
#[test]
fn test_literal() {
#[tokio::test]
async fn test_literal() {
let args = strs_to_strings(&[
"apply_patch",
r#"*** Begin Patch
@@ -497,8 +515,8 @@ mod tests {
}
}
#[test]
fn test_literal_applypatch() {
#[tokio::test]
async fn test_literal_applypatch() {
let args = strs_to_strings(&[
"applypatch",
r#"*** Begin Patch
@@ -522,20 +540,20 @@ mod tests {
}
}
#[test]
fn test_heredoc() {
#[tokio::test]
async fn test_heredoc() {
assert_match(&heredoc_script(""), /*expected_workdir*/ None);
}
#[test]
fn test_heredoc_non_login_shell() {
#[tokio::test]
async fn test_heredoc_non_login_shell() {
let script = heredoc_script("");
let args = strs_to_strings(&["bash", "-c", &script]);
assert_match_args(args, /*expected_workdir*/ None);
}
#[test]
fn test_heredoc_applypatch() {
#[tokio::test]
async fn test_heredoc_applypatch() {
let args = strs_to_strings(&[
"bash",
"-lc",
@@ -562,96 +580,96 @@ PATCH"#,
}
}
#[test]
fn test_powershell_heredoc() {
#[tokio::test]
async fn test_powershell_heredoc() {
let script = heredoc_script("");
assert_match_args(args_powershell(&script), /*expected_workdir*/ None);
}
#[test]
fn test_powershell_heredoc_no_profile() {
#[tokio::test]
async fn test_powershell_heredoc_no_profile() {
let script = heredoc_script("");
assert_match_args(
args_powershell_no_profile(&script),
/*expected_workdir*/ None,
);
}
#[test]
fn test_pwsh_heredoc() {
#[tokio::test]
async fn test_pwsh_heredoc() {
let script = heredoc_script("");
assert_match_args(args_pwsh(&script), /*expected_workdir*/ None);
}
#[test]
fn test_cmd_heredoc_with_cd() {
#[tokio::test]
async fn test_cmd_heredoc_with_cd() {
let script = heredoc_script("cd foo && ");
assert_match_args(args_cmd(&script), Some("foo"));
}
#[test]
fn test_heredoc_with_leading_cd() {
#[tokio::test]
async fn test_heredoc_with_leading_cd() {
assert_match(&heredoc_script("cd foo && "), Some("foo"));
}
#[test]
fn test_cd_with_semicolon_is_ignored() {
#[tokio::test]
async fn test_cd_with_semicolon_is_ignored() {
assert_not_match(&heredoc_script("cd foo; "));
}
#[test]
fn test_cd_or_apply_patch_is_ignored() {
#[tokio::test]
async fn test_cd_or_apply_patch_is_ignored() {
assert_not_match(&heredoc_script("cd bar || "));
}
#[test]
fn test_cd_pipe_apply_patch_is_ignored() {
#[tokio::test]
async fn test_cd_pipe_apply_patch_is_ignored() {
assert_not_match(&heredoc_script("cd bar | "));
}
#[test]
fn test_cd_single_quoted_path_with_spaces() {
#[tokio::test]
async fn test_cd_single_quoted_path_with_spaces() {
assert_match(&heredoc_script("cd 'foo bar' && "), Some("foo bar"));
}
#[test]
fn test_cd_double_quoted_path_with_spaces() {
#[tokio::test]
async fn test_cd_double_quoted_path_with_spaces() {
assert_match(&heredoc_script("cd \"foo bar\" && "), Some("foo bar"));
}
#[test]
fn test_echo_and_apply_patch_is_ignored() {
#[tokio::test]
async fn test_echo_and_apply_patch_is_ignored() {
assert_not_match(&heredoc_script("echo foo && "));
}
#[test]
fn test_apply_patch_with_arg_is_ignored() {
#[tokio::test]
async fn test_apply_patch_with_arg_is_ignored() {
let script = "apply_patch foo <<'PATCH'\n*** Begin Patch\n*** Add File: foo\n+hi\n*** End Patch\nPATCH";
assert_not_match(script);
}
#[test]
fn test_double_cd_then_apply_patch_is_ignored() {
#[tokio::test]
async fn test_double_cd_then_apply_patch_is_ignored() {
assert_not_match(&heredoc_script("cd foo && cd bar && "));
}
#[test]
fn test_cd_two_args_is_ignored() {
#[tokio::test]
async fn test_cd_two_args_is_ignored() {
assert_not_match(&heredoc_script("cd foo bar && "));
}
#[test]
fn test_cd_then_apply_patch_then_extra_is_ignored() {
#[tokio::test]
async fn test_cd_then_apply_patch_then_extra_is_ignored() {
let script = heredoc_script_ps("cd bar && ", " && echo done");
assert_not_match(&script);
}
#[test]
fn test_echo_then_cd_and_apply_patch_is_ignored() {
#[tokio::test]
async fn test_echo_then_cd_and_apply_patch_is_ignored() {
// Ensure preceding commands before the `cd && apply_patch <<...` sequence do not match.
assert_not_match(&heredoc_script("echo foo; cd bar && "));
}
#[test]
fn test_unified_diff_last_line_replacement() {
#[tokio::test]
async fn test_unified_diff_last_line_replacement() {
// Replace the very last line of the file.
let dir = tempdir().unwrap();
let path = dir.path().join("last.txt");
@@ -674,7 +692,10 @@ PATCH"#,
_ => panic!("Expected a single UpdateFile hunk"),
};
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
let path_abs = path.as_path().abs();
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
.await
.unwrap();
let expected_diff = r#"@@ -2,2 +2,2 @@
bar
-baz
@@ -687,8 +708,8 @@ PATCH"#,
assert_eq!(expected, diff);
}
#[test]
fn test_unified_diff_insert_at_eof() {
#[tokio::test]
async fn test_unified_diff_insert_at_eof() {
// Insert a new line at endoffile.
let dir = tempdir().unwrap();
let path = dir.path().join("insert.txt");
@@ -709,7 +730,10 @@ PATCH"#,
_ => panic!("Expected a single UpdateFile hunk"),
};
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
let path_abs = path.as_path().abs();
let diff = unified_diff_from_chunks(&path_abs, chunks, LOCAL_FS.as_ref())
.await
.unwrap();
let expected_diff = r#"@@ -3 +3,2 @@
baz
+quux
@@ -721,8 +745,8 @@ PATCH"#,
assert_eq!(expected, diff);
}
#[test]
fn test_apply_patch_should_resolve_absolute_paths_in_cwd() {
#[tokio::test]
async fn test_apply_patch_should_resolve_absolute_paths_in_cwd() {
let session_dir = tempdir().unwrap();
let relative_path = "source.txt";
@@ -742,7 +766,12 @@ PATCH"#,
.to_string(),
];
let result = maybe_parse_apply_patch_verified(&argv, session_dir.path());
let result = maybe_parse_apply_patch_verified(
&argv,
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
LOCAL_FS.as_ref(),
)
.await;
// Verify the patch contents - as otherwise we may have pulled contents
// from the wrong file (as we're using relative paths)
@@ -762,13 +791,13 @@ PATCH"#,
},
)]),
patch: argv[1].clone(),
cwd: session_dir.path().to_path_buf(),
cwd: AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
})
);
}
#[test]
fn test_apply_patch_resolves_move_path_with_effective_cwd() {
#[tokio::test]
async fn test_apply_patch_resolves_move_path_with_effective_cwd() {
let session_dir = tempdir().unwrap();
let worktree_rel = "alt";
let worktree_dir = session_dir.path().join(worktree_rel);
@@ -790,13 +819,18 @@ PATCH"#,
let shell_script = format!("cd {worktree_rel} && apply_patch <<'PATCH'\n{patch}\nPATCH");
let argv = vec!["bash".into(), "-lc".into(), shell_script];
let result = maybe_parse_apply_patch_verified(&argv, session_dir.path());
let result = maybe_parse_apply_patch_verified(
&argv,
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
LOCAL_FS.as_ref(),
)
.await;
let action = match result {
MaybeApplyPatchVerified::Body(action) => action,
other => panic!("expected verified body, got {other:?}"),
};
assert_eq!(action.cwd, worktree_dir);
assert_eq!(action.cwd.as_path(), worktree_dir.as_path());
let change = action
.changes()