From 1a1516a80bc71662686e7716bf87a0894bba110d Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 20 Aug 2025 12:16:01 -0700 Subject: [PATCH] [apply-patch] Fix applypatch for heredocs (#2477) ## Summary Follow up to #2186 for #2072 - we added handling for `applypatch` in default commands, but forgot to add detection to the heredocs logic. ## Testing - [x] Added unit tests --- codex-rs/apply-patch/src/lib.rs | 39 ++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index 1aae1066..15966ac2 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -22,6 +22,8 @@ use tree_sitter_bash::LANGUAGE as BASH; /// Detailed instructions for gpt-4.1 on how to use the `apply_patch` tool. pub const APPLY_PATCH_TOOL_INSTRUCTIONS: &str = include_str!("../apply_patch_tool_instructions.md"); +const APPLY_PATCH_COMMANDS: [&str; 2] = ["apply_patch", "applypatch"]; + #[derive(Debug, Error, PartialEq)] pub enum ApplyPatchError { #[error(transparent)] @@ -82,7 +84,6 @@ pub struct ApplyPatchArgs { } pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch { - const APPLY_PATCH_COMMANDS: [&str; 2] = ["apply_patch", "applypatch"]; match argv { [cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => match parse_patch(body) { Ok(source) => MaybeApplyPatch::Body(source), @@ -91,7 +92,9 @@ pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch { [bash, flag, script] if bash == "bash" && flag == "-lc" - && script.trim_start().starts_with("apply_patch") => + && APPLY_PATCH_COMMANDS + .iter() + .any(|cmd| script.trim_start().starts_with(cmd)) => { match extract_heredoc_body_from_apply_patch_command(script) { Ok(body) => match parse_patch(&body) { @@ -262,7 +265,10 @@ pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApp fn extract_heredoc_body_from_apply_patch_command( src: &str, ) -> std::result::Result { - if !src.trim_start().starts_with("apply_patch") { + if !APPLY_PATCH_COMMANDS + .iter() + .any(|cmd| src.trim_start().starts_with(cmd)) + { return Err(ExtractHeredocError::CommandDidNotStartWithApplyPatch); } @@ -773,6 +779,33 @@ PATCH"#, } } + #[test] + fn test_heredoc_applypatch() { + let args = strs_to_strings(&[ + "bash", + "-lc", + r#"applypatch <<'PATCH' +*** Begin Patch +*** Add File: foo ++hi +*** End Patch +PATCH"#, + ]); + + match maybe_parse_apply_patch(&args) { + MaybeApplyPatch::Body(ApplyPatchArgs { hunks, patch: _ }) => { + assert_eq!( + hunks, + vec![Hunk::AddFile { + path: PathBuf::from("foo"), + contents: "hi\n".to_string() + }] + ); + } + result => panic!("expected MaybeApplyPatch::Body got {result:?}"), + } + } + #[test] fn test_add_file_hunk_creates_file_with_contents() { let dir = tempdir().unwrap();