fix(apply-patch): preserve CRLF line endings on Windows (#7515)

## Summary
This PR is heavily based on #4017, which contains the core logic for the
fix. To reduce the risk, we are first introducing it only on windows. We
can then expand to wsl / other environments as needed, and then tackle
net new files.

## Testing
- [x] added unit tests in apply-patch
- [x] add integration tests to apply_patch_cli.rs

---------

Co-authored-by: Chase Naples <Cnaples79@gmail.com>
This commit is contained in:
Dylan Hurd
2025-12-05 16:43:27 -08:00
committed by GitHub
parent 952d6c9465
commit 6c9c563faf
2 changed files with 244 additions and 8 deletions

View File

@@ -1250,3 +1250,94 @@ async fn apply_patch_change_context_disambiguates_target(
assert_eq!(contents, "fn a\nx=10\ny=2\nfn b\nx=11\ny=20\n");
Ok(())
}
/// Ensure that applying a patch can update a CRLF file with unicode characters.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[test_case(ApplyPatchModelOutput::Freeform)]
#[test_case(ApplyPatchModelOutput::Function)]
#[test_case(ApplyPatchModelOutput::Shell)]
#[test_case(ApplyPatchModelOutput::ShellViaHeredoc)]
#[test_case(ApplyPatchModelOutput::ShellCommandViaHeredoc)]
async fn apply_patch_cli_updates_unicode_characters(
model_output: ApplyPatchModelOutput,
) -> Result<()> {
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness().await?;
let target = harness.path("unicode.txt");
fs::write(&target, "first ⚠️\nsecond ❌\nthird 🔥\n")?;
let patch = format!(
r#"*** Begin Patch
*** Update File: {}
@@
first ⚠️
-second ❌
+SECOND ✅
@@
third 🔥
+FOURTH
*** End of File
*** End Patch"#,
target.display()
);
let call_id = "apply-unicode-update";
mount_apply_patch(&harness, call_id, patch.as_str(), "ok", model_output).await;
harness
.submit("update unicode characters via apply_patch CLI")
.await?;
let file_contents = fs::read(&target)?;
let content = String::from_utf8_lossy(&file_contents);
assert_eq!(content, "first ⚠️\nSECOND ✅\nthird 🔥\nFOURTH\n");
Ok(())
}
/// Ensure that applying a patch via the CLI preserves CRLF line endings for
/// Windows-style inputs even when updating the file contents.
#[cfg(windows)]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[test_case(ApplyPatchModelOutput::Freeform)]
#[test_case(ApplyPatchModelOutput::Function)]
#[test_case(ApplyPatchModelOutput::Shell)]
#[test_case(ApplyPatchModelOutput::ShellViaHeredoc)]
#[test_case(ApplyPatchModelOutput::ShellCommandViaHeredoc)]
async fn apply_patch_cli_updates_crlf_file_preserves_line_endings(
model_output: ApplyPatchModelOutput,
) -> Result<()> {
skip_if_no_network!(Ok(()));
let harness = apply_patch_harness().await?;
let target = harness.path("crlf.txt");
fs::write(&target, b"first\r\nsecond\r\nthird\r\n")?;
let patch = format!(
r#"*** Begin Patch
*** Update File: {}
@@
first
-second
+SECOND
@@
third
+FOURTH
*** End of File
*** End Patch"#,
target.display()
);
let call_id = "apply-crlf-update";
mount_apply_patch(&harness, call_id, patch.as_str(), "ok", model_output).await;
harness
.submit("update crlf file via apply_patch CLI")
.await?;
let file_contents = fs::read(&target)?;
let content = String::from_utf8_lossy(&file_contents);
assert!(content.contains("\r\n"));
assert_eq!(content, "first\r\nSECOND\r\nthird\r\nFOURTH\r\n");
Ok(())
}