Skip to content

Pick inserted line terminators through line_endings::terminator (#815) - #1108

Merged
Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
arch-refactor/815-line-terminator
Oct 8, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
arch-refactor/815-line-terminator

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Refs #815. This is slice 1 of 2; the issue stays open for slice 2.

Summary

Writers that insert lines into a user's file each hand-wrote "if the text contains any \r\n, use CRLF". This PR adds one rule, utils::line_endings::terminator(text), and routes the 7 copies outside open-PR files through it:

  • CRLF file → \r\n;
  • LF file, or no break at all → \n;
  • mixed file → the existing majority_terminator.

Why (leverage)

What changed

Site Before After
formats/gem/hosted.rs converge_gem_lock_source (hosted Gemfile.lock pin) inline any-CRLF terminator
redirect/upstream/composer.rs (composer.lock dist/source restore) inline any-CRLF terminator
redirect/upstream/pypi.rs (requirements --hash continuation restore) inline any-CRLF terminator
redirect/pipenv.rs format_entry (Pipfile.lock forward and restore) inline any-CRLF terminator
utils/python_script.rs replace_script_metadata (PEP 723, forward and restore) inline any-CRLF terminator
vendor/go_mod_edit.rs append + join_preserving_trailing_newline common::detect_eol ×2 terminator

Deleted: the 6 inline copies and both go_mod_edit calls to detect_eol. Production: about +25 / −12 lines. Tests: about +100 lines. Goldens: about 240 lines re-blessed.

Left for slice 2 (every site sits in a file another open PR changes): vendor::common::detect_eol and its callers (go.sum, requirements, yarn classic), pypi_uv::newline_of, maven_reactor.rs ×2, redirect/mod.rs, the crlf flags in upstream/cargo.rs, npmrc.rs and pypi_pipenv.rs, and Gradle's first-line newline_of. Upstream gem's restore_manifest is deliberately left out too: manifest_blocks_restore_in_place pins CRLF output for a CRLF Gemfile that holds an older rewriter's LF block, so it has to move together with its forward Gemfile writer in redirect/mod.rs.

Behavior

  • LF-only and CRLF-only files: none. terminator agrees with the old rule whenever the file isn't mixed.
  • Mixed-ending files (the change Pick the line terminator for spliced lines through one line_endings::terminator #815 asks for): inserted lines now take the majority style. Before, a single \r\n anywhere made every inserted line CRLF. The two rules differ only on a mixed file whose majority is LF. A mixed LF-majority composer.lock now unwinds byte for byte; before, its restored dist block came back CRLF.
  • Goldens: the go and uv equivalence generators turn one input in five into a mixed file (every 4th line CRLF), so those cases moved: 194 of 300 golang_rewrite chunks (plus 13 more from the follow-up below) and 19 of 300 python_lock_rewrite chunks (20 cases per chunk). No LF-only or CRLF-only case can move, per the rule above.
  • No change to JSON output, error codes, exit codes or the CLI contract.

Test evidence

  • New tests, all on mixed inputs:
    • unit tests: terminator_follows_the_file_and_the_majority_when_mixed, formatted_entry_takes_the_majority_line_ending, replaced_metadata_takes_the_majority_line_ending, mixed_go_mod_append_takes_the_majority_line_ending, restored_hash_continuation_takes_the_majority_line_ending;
    • tests/upstream_restore_golden.rs: composer_mixed_line_endings_restore_in_the_majority_style (byte-exact round trip) and gem_lock_pin_on_a_mixed_lock_takes_the_majority_terminator.
  • Red → green: with terminator temporarily given the old "any CRLF" rule, all 6 per-site tests fail (4 lib + 2 integration). With the new rule, all pass.
  • cargo test -p socket-patch-core --lib: 5667 passed, 4 failed. All 4 are the known root-only sandbox failures that also fail on main: relax_loop_must_not_traverse_symlinked_root, an_unremovable_hidden_lock_keeps_every_store_entry, wire_write_failure_maps_error_and_leaves_lock_untouched, wire_failure_rolls_back_already_written_files. After the follow-up, the go/golden/equivalence subset passes 248/248.
  • cargo test -p socket-patch-core --test upstream_restore_golden: 50 passed.
  • cargo clippy --workspace --all-features -- -D warnings: clean, before and after the follow-up.
  • rustfmt: every touched file is rustfmt-clean.
  • Not run locally: cargo test --workspace --all-features, because linking all ~235 CLI test binaries filled the sandbox disk. CI covers the CLI suites.

Review follow-up

  • Bugbot (311462c): on an LF-majority go.mod whose trailing blank line is CRLF, the append missed the blank and wrote a second one. Fixed in 6b72d7f: the append detects a trailing blank whichever break spells it. Covered by an extra case in mixed_go_mod_append_takes_the_majority_line_ending. Bugbot is clean on 6b72d7f.
  • CI on 6b72d7f: 415 of 416 checks passing or skipped at hand-off, with one Gradle e2e leg still running. The ci-ok failure on 311462c is the aggregator of runs cancelled by the 6b72d7f push.

Risk

Low. Seven one-line call-site swaps behind a 6-line pure function. The only behavior change is on mixed-ending files, as #815 specifies. The wrappers (npm/, pypi/, gem/) are unaffected.

🤖 Generated with Claude Code


Note

Low Risk
Behavior change is limited to mixed line-ending files; uniform LF/CRLF paths match the old rule. Wide but shallow call-site swaps with strong test/golden coverage.

Overview
Introduces line_endings::terminator as the single rule for which break style writers use when inserting lines: pure CRLF → \r\n, pure LF (or no breaks) → \n, mixed files → existing majority count (ties → LF).

Replaces seven ad hoc contains("\r\n") / detect_eol call sites (Gemfile.lock converge, Pipenv lock formatting, composer.lock restore, requirements hash continuations, PEP 723 metadata re-commenting, go.mod append/join) so a lone CRLF line no longer forces all new lines to CRLF on LF-majority mixed files.

go_mod_edit also treats a trailing blank line as present when its break is the minority style, avoiding a double blank before an appended replace directive.

Adds targeted unit/integration tests for mixed inputs and re-blesses golang / python lock equivalence goldens where mixed-ending fixtures changed outputs.

Reviewed by Cursor Bugbot for commit 47a45fa. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko Mikola Lysenko (mikolalysenko) added refactor Structural change: duplicated code or logic, missing abstraction, layering, dead code arch-refactor PR opened by the scheduled architecture refactor routine labels Oct 8, 2026
Add utils::line_endings::terminator and route seven writers that
hand-wrote "any \r\n means CRLF" through it: the hosted gem lock
converge, the composer and requirements restores, the Pipfile.lock
entry formatter, the PEP 723 metadata writer and the go.mod
append/re-join. LF-only and CRLF-only files are unchanged. On a file
with mixed endings, new lines now take the majority style, so one
stray CRLF line no longer turns every inserted line CRLF and a
mixed composer.lock restores byte for byte.

Refs #815

Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
The golang_rewrite and python_lock_rewrite generators turn one input
in five into a mixed-ending file (every fourth line CRLF, so LF is the
majority). On those inputs the go.mod append and the uv script block
now insert LF lines instead of CRLF ones. terminator differs from the
old rule only for a mixed file whose majority is LF, so no LF-only or
CRLF-only case moved.

Refs #815

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 8, 2026 04:27
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 8, 2026
Assisted-by: Claude Code:claude-opus-5-5

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/vendor/go_mod_edit.rs
On an LF-majority go.mod whose last blank line is CRLF, the append's
ends_with("\n\n") test missed the blank and wrote a second one before
the replace directive, the shape go mod tidy churns. Detect a
trailing blank line whichever break spells it. Uniform files are
unchanged; the go golden moves for the mixed cases that end that way.

Refs #815

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

The only conflict was tests/equivalence/golang_rewrite.golden. Both
sides re-blessed it: this branch because go.mod appends now take their
terminator from line_endings::terminator, and main (#1035) because the
superseded-gosum cleanup changes the go.sum output. A textual pick of
either side would drop the other's behavior, so the golden was re-blessed
on the merged code. Lines only one side changed keep that side's
digest; the 53 lines both sides changed get new digests.

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Brings the branch up to main 823810a. The merge is clean; the earlier
re-blessed golang_rewrite golden still matches the merged code (verified
by re-blessing on this tree with no change).

Co-Authored-By: Claude <noreply@anthropic.com>
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 47a45fa. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] native (ubuntu-latest, 1.3.9) on 47a45fa failed one Bun backtest cell: workspace vendored (refusalCodesExact). The other 52 cells in that job passed. This doesn't look like this PR's failure:

  • The diff only changes the gem, composer, pypi, pipenv, PEP 723 and go.mod line-terminator writers. It touches no Bun or npm-workspace code.
  • The same job passed on 6b72d7f, this PR's previous head, and on main at 823810a. 47a45fa is exactly those two commits merged.
  • The other Bun versions on this head passed the same cell.

No fix to port. I'm re-running the failed job once. If it fails again, I'll treat it as real and dig in.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 8, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at 47a45fa.

  • CI: 403/403 non-skipped checks green (ci-ok success), 14 skipped
  • Bugbot: reviewed 47a45fa, no unresolved findings.
  • Mergeable: yes, no conflicts with base.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] The merge queue removed this PR after run 37834367420 failed. The only failed job is the Gradle e2e leg (e2e (ubuntu-latest, e2e_gradle_discovery_build …)), and it failed in the Install Gradle 6.9.4 step: downloading gradle-6.9.4-bin.zip from services.gradle.org, before any test ran. ci-ok failed only because that job failed. The other 211 jobs passed. This doesn't look like this PR's failure, and there's no code fix to make. The PR head (47a45fa) is still green and mergeable. It needs to be put back in the merge queue; I can't do that from here.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 8, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[final reviewer] Re-enqueued (auto-merge on, squash) at head 47a45fac. It was evicted at 20:12 UTC on the Gradle 6.9.4 install download failure noted above, before any test ran. This is the one re-queue.


Generated by Claude Code

Merged via the queue into main with commit ef48495 Oct 8, 2026
451 of 452 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the arch-refactor/815-line-terminator branch October 8, 2026 22:55
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 8, 2026
Mikola Lysenko (mikolalysenko) pushed a commit that referenced this pull request Oct 9, 2026
Vendored go.sum, yarn classic, requirements and uv writers, and the
hosted .npmrc splice, now ask utils::line_endings::terminator which
line ending to write. The private "any CRLF means CRLF" copies
(vendor::common::detect_eol, pypi_uv::newline_of and the inline
.npmrc rule) are deleted.

LF-only and CRLF-only files are written exactly as before. A file
that mixes CRLF and LF breaks now gets new lines in its majority
style (a tie is LF) instead of CRLF whenever any CRLF appears, the
rule the other writers already use since #1108. The golang
equivalence golden is re-blessed: only its mixed go.sum inputs move.

Refs #815

Assisted-by: Claude Code:claude-opus-5-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-refactor PR opened by the scheduled architecture refactor routine Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review refactor Structural change: duplicated code or logic, missing abstraction, layering, dead code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants