Skip to content

fix: preserve CRLF line endings in 8v write append and find-replace#4

Merged
xsoheilalizadeh merged 4 commits intomainfrom
fix/crlf-line-ending-preservation
Apr 27, 2026
Merged

fix: preserve CRLF line endings in 8v write append and find-replace#4
xsoheilalizadeh merged 4 commits intomainfrom
fix/crlf-line-ending-preservation

Conversation

@xsoheilalizadeh
Copy link
Copy Markdown
Contributor

Summary

  • Append now detects the file's line ending under the existing exclusive flock by reading the last two bytes (instead of one), so the separator and trailing terminator match CRLF or LF as the file dictates. Bare \n in content is normalized to \r\n when the file is CRLF.
  • FindReplace now converts \n to \r\n in both find and replace strings when the file is CRLF, closing the silent-corruption gap where a \n in the replacement on a CRLF file produced mixed endings.
  • Adds five regression tests in e2e_write.rs covering Append, FindReplace, Insert, and Delete on CRLF files.

Closes #3.

Stacked on #2 (the race fix added the flock infra this builds on). Will auto-retarget to main when #2 merges.

Test plan

  • cargo test -p o8v --test e2e_write — 47 pass, 0 fail
  • cargo test --workspace — no regressions
  • cargo clippy --workspace -- -D warnings — clean
  • CI green

@xsoheilalizadeh xsoheilalizadeh added bug Something doesn't work as intended area/write o8v write command and o8v-fs writes labels Apr 27, 2026
xsoheilalizadeh added a commit that referenced this pull request Apr 27, 2026
Adds 10 tests covering the gaps surfaced by the three review rounds on
PR #4: CRLF file without trailing newline, mixed-ending input, boundary
file sizes (0/1/2 bytes), find-replace with newline in pattern, and a
CRLF concurrent stress test. Tests that exercise currently-broken
behavior are #[ignore]'d and will be un-ignored when the underlying
bugs are fixed.

Refs #3.
Closes #3.

Append now detects the file's line ending under the existing exclusive
flock by reading the last two bytes (instead of one), so the separator
and trailing terminator match CRLF or LF as the file dictates. Content
passed via --append is normalized to the file's detected ending if the
user passes pure-LF content into a CRLF file.

FindReplace now validates the replacement string with the same rules as
other content-injecting modes (rejects \r\n and lone \r in the
replacement), and converts \n to \r\n in the replacement when the file
is CRLF. This closes the silent-corruption gap where a \n in the
replacement on a CRLF file produced mixed endings.

Adds five regression tests in e2e_write.rs covering Append, FindReplace,
Insert, and Delete on CRLF files.
Adds 10 tests covering the gaps surfaced by the three review rounds on
PR #4: CRLF file without trailing newline, mixed-ending input, boundary
file sizes (0/1/2 bytes), find-replace with newline in pattern, and a
CRLF concurrent stress test. Tests that exercise currently-broken
behavior are #[ignore]'d and will be un-ignored when the underlying
bugs are fixed.

Refs #3.
The previous append fix sampled only the last 2 bytes to detect CRLF —
insufficient when a CRLF file lacks a trailing \r\n (e.g., written by a
non-8v tool, or where the last line was authored without a final
newline). Now reads the full file under the existing exclusive flock,
scans for any \r\n to detect CRLF, and validates the existing content
has no mixed endings before appending. The full read happens inside the
flock so it remains race-safe.

Inlines a byte-level mixed-ending check in write_guard.rs (option b) —
the rule is two comparisons on a Vec<u8> already in hand, so moving it
to a separate helper would add surface area without clarity benefit.

Un-ignores 4 tests pinned in 98efedc:
- append_to_crlf_file_without_trailing_newline_preserves_crlf
- append_then_subsequent_write_succeeds_on_crlf_file_without_trailing_newline
- append_on_pre_existing_mixed_ending_file_is_rejected
- append_concurrent_50_crlf_seed_no_trailing_newline_preserves_crlf

Refs #3.
validate_line_endings was inlined in two places — write_guard.rs (byte
level, only catching mixed CRLF+LF) and write.rs (str level, catching
CR-only files, lone-\r in LF files, and mixed CRLF+LF). The two
implementations diverged: the byte-level inline version missed the
lone-\r cases. Extract validate_line_endings_bytes into o8v-fs so both
call sites share the same logic and produce the same error messages.

Also fixes the stale doc on guarded_append_with_separator that still
described the replaced "last 2 bytes" approach.
@xsoheilalizadeh xsoheilalizadeh force-pushed the fix/crlf-line-ending-preservation branch from d04d2c0 to c86cedf Compare April 27, 2026 22:21
@xsoheilalizadeh xsoheilalizadeh changed the base branch from fix/ci-restore-test-fixtures to main April 27, 2026 22:28
@xsoheilalizadeh xsoheilalizadeh merged commit 5fef036 into main Apr 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/write o8v write command and o8v-fs writes bug Something doesn't work as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8v write: line-ending preservation should be consistent across all modes

1 participant