From b67d666929fb3c7683be071e6e2c1465a5110a95 Mon Sep 17 00:00:00 2001 From: Calvin Tan - aelf Date: Fri, 12 Jun 2026 11:18:13 +0800 Subject: [PATCH] Preserve newline style in apply-patch updates (#2) Closes #1. Co-authored-by: Claude Opus 4.8 (1M context) --- codex-rs/apply-patch/src/lib.rs | 23 +++++++++++++++++-- .../tests/fixtures/scenarios/.gitattributes | 2 ++ .../expected/lines.txt | 3 +++ .../023_update_preserves_crlf/input/lines.txt | 3 +++ .../023_update_preserves_crlf/patch.txt | 10 ++++++++ .../expected/lines.txt | 3 +++ .../024_update_preserves_lf/input/lines.txt | 3 +++ .../024_update_preserves_lf/patch.txt | 10 ++++++++ 8 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/expected/lines.txt create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/input/lines.txt create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/patch.txt create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/expected/lines.txt create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/input/lines.txt create mode 100644 codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/patch.txt diff --git a/codex-rs/apply-patch/src/lib.rs b/codex-rs/apply-patch/src/lib.rs index 29d42e1c072b..3a56306141a1 100644 --- a/codex-rs/apply-patch/src/lib.rs +++ b/codex-rs/apply-patch/src/lib.rs @@ -664,7 +664,11 @@ async fn derive_new_contents_from_chunks( }) })?; - let mut original_lines: Vec = original_contents.split('\n').map(String::from).collect(); + let line_separator = detect_line_separator(&original_contents); + let mut original_lines: Vec = original_contents + .split('\n') + .map(|line| line.strip_suffix('\r').unwrap_or(line).to_string()) + .collect(); // Drop the trailing empty element that results from the final newline so // that line counts match the behaviour of standard `diff`. @@ -678,13 +682,28 @@ async fn derive_new_contents_from_chunks( if !new_lines.last().is_some_and(String::is_empty) { new_lines.push(String::new()); } - let new_contents = new_lines.join("\n"); + let new_contents = new_lines.join(line_separator); Ok(AppliedPatch { original_contents, new_contents, }) } +fn detect_line_separator(contents: &str) -> &'static str { + let lf_count = contents + .as_bytes() + .iter() + .filter(|byte| **byte == b'\n') + .count(); + let crlf_count = contents.match_indices("\r\n").count(); + + if lf_count > 0 && lf_count == crlf_count { + "\r\n" + } else { + "\n" + } +} + /// Compute a list of replacements needed to transform `original_lines` into the /// new lines, given the patch `chunks`. Each replacement is returned as /// `(start_index, old_len, new_lines)`. diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/.gitattributes b/codex-rs/apply-patch/tests/fixtures/scenarios/.gitattributes index a42a20ddc51d..94888d559b9e 100644 --- a/codex-rs/apply-patch/tests/fixtures/scenarios/.gitattributes +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/.gitattributes @@ -1 +1,3 @@ ** text eol=lf +023_update_preserves_crlf/input/lines.txt text eol=crlf +023_update_preserves_crlf/expected/lines.txt text eol=crlf diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/expected/lines.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/expected/lines.txt new file mode 100644 index 000000000000..e50310a98706 --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/expected/lines.txt @@ -0,0 +1,3 @@ +alpha +BETA +gamma diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/input/lines.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/input/lines.txt new file mode 100644 index 000000000000..85c30401ce28 --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/input/lines.txt @@ -0,0 +1,3 @@ +alpha +beta +gamma diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/patch.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/patch.txt new file mode 100644 index 000000000000..cbf71d5ebe1d --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/023_update_preserves_crlf/patch.txt @@ -0,0 +1,10 @@ +*** Begin Patch +*** Update File: lines.txt +@@ +-alpha +-beta +-gamma ++alpha ++BETA ++gamma +*** End Patch diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/expected/lines.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/expected/lines.txt new file mode 100644 index 000000000000..e50310a98706 --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/expected/lines.txt @@ -0,0 +1,3 @@ +alpha +BETA +gamma diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/input/lines.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/input/lines.txt new file mode 100644 index 000000000000..85c30401ce28 --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/input/lines.txt @@ -0,0 +1,3 @@ +alpha +beta +gamma diff --git a/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/patch.txt b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/patch.txt new file mode 100644 index 000000000000..cbf71d5ebe1d --- /dev/null +++ b/codex-rs/apply-patch/tests/fixtures/scenarios/024_update_preserves_lf/patch.txt @@ -0,0 +1,10 @@ +*** Begin Patch +*** Update File: lines.txt +@@ +-alpha +-beta +-gamma ++alpha ++BETA ++gamma +*** End Patch