This commit is contained in:
Dylan Hurd
2026-06-11 20:14:42 -07:00
parent 2662873a60
commit bee22e6756
5 changed files with 71 additions and 7 deletions

View File

@@ -44,6 +44,9 @@ use crate::invocation::ExtractHeredocError;
/// surface.
pub const CODEX_CORE_APPLY_PATCH_ARG1: &str = "--codex-run-as-apply-patch";
/// Enables CRLF preservation for the standalone `apply_patch` command.
pub const APPLY_PATCH_PRESERVE_CRLF_ARG: &str = "--preserve-crlf";
/// Controls optional behavior when parsing or applying a patch.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ApplyPatchOptions {

View File

@@ -442,8 +442,8 @@ fn parse_update_file_chunk(
match line_contents.chars().next() {
None => {
// Interpret this as an empty line.
chunk.old_lines.push(String::new());
chunk.new_lines.push(String::new());
chunk.old_lines.push(line.to_string());
chunk.new_lines.push(line.to_string());
}
Some(' ') => {
chunk.old_lines.push(line[1..].to_string());
@@ -587,6 +587,34 @@ fn test_update_file_chunk() {
);
}
#[test]
fn test_update_file_chunk_preserves_crlf_for_bare_empty_context_line() {
assert_eq!(
parse_update_file_chunk(
&["@@\r", " before\r", "\r", " after\r", "*** End Patch\r"],
/*line_number*/ 123,
/*allow_missing_context*/ false,
),
Ok((
UpdateFileChunk {
change_context: None,
old_lines: vec![
"before\r".to_string(),
"\r".to_string(),
"after\r".to_string(),
],
new_lines: vec![
"before\r".to_string(),
"\r".to_string(),
"after\r".to_string(),
],
is_end_of_file: false,
},
4,
))
);
}
#[test]
fn test_parse_patch() {
assert_eq!(

View File

@@ -1,8 +1,6 @@
use std::io::Read;
use std::io::Write;
const PRESERVE_CRLF_FLAG: &str = "--preserve-crlf";
pub fn main() -> ! {
let exit_code = run_main();
std::process::exit(exit_code);
@@ -17,7 +15,7 @@ pub fn run_main() -> i32 {
let _argv0 = args.next();
let first_arg = args.next();
let (options, patch_arg) = match first_arg {
Some(arg) if arg.to_str() == Some(PRESERVE_CRLF_FLAG) => {
Some(arg) if arg == crate::APPLY_PATCH_PRESERVE_CRLF_ARG => {
(crate::ApplyPatchOptions::preserve_crlf(), args.next())
}
patch_arg => (crate::ApplyPatchOptions::default(), patch_arg),

View File

@@ -4,6 +4,7 @@ use std::future::Future;
use std::path::Path;
use std::path::PathBuf;
use codex_apply_patch::APPLY_PATCH_PRESERVE_CRLF_ARG;
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
use codex_exec_server::CODEX_FS_HELPER_ARG1;
use codex_install_context::InstallContext;
@@ -100,7 +101,15 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
codex_exec_server::run_fs_helper_main();
}
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 {
let patch_arg = args.next().and_then(|s| s.to_str().map(str::to_owned));
let first_arg = args.next();
let (options, patch_arg) = match first_arg {
Some(arg) if arg == APPLY_PATCH_PRESERVE_CRLF_ARG => (
codex_apply_patch::ApplyPatchOptions::preserve_crlf(),
args.next(),
),
patch_arg => (codex_apply_patch::ApplyPatchOptions::default(), patch_arg),
};
let patch_arg = patch_arg.and_then(|s| s.to_str().map(str::to_owned));
let exit_code = match patch_arg {
Some(patch_arg) => {
let mut stdout = std::io::stdout();
@@ -116,8 +125,9 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
Ok(runtime) => runtime,
Err(_) => std::process::exit(1),
};
match runtime.block_on(codex_apply_patch::apply_patch(
match runtime.block_on(codex_apply_patch::apply_patch_with_options(
&patch_arg,
options,
&cwd,
&mut stdout,
&mut stderr,

View File

@@ -44,6 +44,31 @@ fn test_standalone_exec_cli_can_use_apply_patch() -> anyhow::Result<()> {
Ok(())
}
#[test]
fn test_standalone_exec_cli_can_preserve_crlf() -> anyhow::Result<()> {
let tmp = tempdir()?;
let relative_path = "source.txt";
let absolute_path = tmp.path().join(relative_path);
fs::write(&absolute_path, "original content\r\n")?;
Command::new(codex_utils_cargo_bin::cargo_bin("codex-exec")?)
.arg(CODEX_CORE_APPLY_PATCH_ARG1)
.arg("--preserve-crlf")
.arg(
"*** Begin Patch\r\n*** Update File: source.txt\r\n@@\r\n-original content\r\n+modified by apply_patch\r\n*** End Patch\r\n",
)
.current_dir(tmp.path())
.assert()
.success()
.stdout("Success. Updated the following files:\nM source.txt\n")
.stderr(predicates::str::is_empty());
assert_eq!(
fs::read_to_string(absolute_path)?,
"modified by apply_patch\r\n"
);
Ok(())
}
#[cfg(not(target_os = "windows"))]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_apply_patch_tool() -> anyhow::Result<()> {