Skip to content

Give back a Cargo config with no final newline on hosted remove - #2078

Open
Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
mainfrom
fix/cargo-hosted-config-final-newline
Open

Mikola Lysenko (mikolalysenko) wants to merge 3 commits into
mainfrom
fix/cargo-hosted-config-final-newline

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Hosted scan followed by remove did not give back a .cargo/config.toml that had no final newline. The config came back with a newline added:

before scan:   [net]\nretry = 2
after scan:    [net]\nretry = 2\n\n[registries.socket-patch-<uuid>]\nindex = "…"\n
after remove:  [net]\nretry = 2\n

The rewriter appended two newlines before its [registries.…] block in that case: one to end the user's last line and one as a blank separator. remove takes back the block and one newline. The edit record carried the extra newline for the ledger-based revert older CLIs had. v5 hosted mode keeps no ledger, and nothing in the file tells this config apart from one that ended in a newline, so the extra newline stayed. e2e_redirect_cargo_shapes knew about this and tolerated it.

Fix

The rewriter now puts exactly one newline before the block whatever the config ends with. After a final newline that is the blank separator, as before. After none it is the line ending the config lacked, and the block follows the last line directly:

[net]
retry = 2
[registries.socket-patch-<uuid>]
index = "…"

That is valid TOML, and taking back the block plus one newline gives the original bytes in both cases.

The cost is the blank line before the block in a config with no final newline. I chose exact restore over that blank line because remove has no other way to know what the file ended with.

Review

/code-review high reported eight findings. Seven are fixed here, and remove_registry_block was rewritten for them (it now locates the block by line, and remove_appended_cargo_block is folded into it):

  • A regression from the first commit of this PR. With the block straight after the user's last line, content the user later put directly after the block got glued to that line on remove (retry = 3[build]), which cargo cannot parse. The newline before the block now stays when taking it would join two lines.
  • Mixed line endings. A config with some CRLF and some LF lines came back all CRLF. Only a file whose every line ends in CRLF is converted now, the rule the rewriter uses.
  • A comment heading the next section was deleted with the block. Blank lines and comments after the block's last key are the user's and stay.
  • No golden covered a config without a final newline. Added tests/fixtures/redirect/cargo/cargo/config-unterminated, which the rewrite golden, the restore round-trip golden and the vex discovery golden all run.
  • A test that passed without the fix. restore_gives_back_a_config_with_no_final_newline now takes its hosted bytes from the rewriter.
  • Two stale doc comments.

Not fixed:

  • A config that was empty or only whitespace before hosting is deleted on remove. With nothing but whitespace left, the file can't be told apart from one the rewriter created and an editor later touched, and deleting a config that holds nothing is the existing cleanup rule. Older than this PR.
  • A project hosted by an earlier release already has the two-newline form on disk. Removing that pin still leaves the final newline, for the reason above.

Follow-up for depscan

depscan's TypeScript rewriter (workspaces/app/src/patches/registry-rewrite/cargo.ts) has the same two-newline append. With the new golden, its parity test will fail on depscan's next socket-patch bump until the rewriter follows or the case is listed in TS_LAGGING.

Testing

  • cargo_config_append_is_undone_byte_for_byte: the rewriter's append and the removal over six configs (final newline, none, trailing blank lines, comment only, CRLF with and without a final newline), then two patches appended to an unterminated config and removed in either order.
  • content_after_the_block_is_kept and mixed_line_endings_are_left_as_they_are cover the review findings.
  • e2e_redirect_cargo_shapes no longer tolerates an added final newline. Its config-unterminated shape passes with real cargo 1.93.1.
  • cargo test -p socket-patch-core --lib: 7,741 passed. The rewrite and restore goldens pass with the new fixture, as do the hosted Cargo CLI suites (e2e_redirect_cargo_shapes, e2e_redirect_cargo_build, in_process_redirect, hosted_memory_engine, mode_migration_cargo, e2e_vex_redirect).
  • cargo fmt --check and cargo clippy --locked --workspace --all-features -- -D warnings are clean.
  • depscan's Cargo capture harness, whose rollback_byte_identical check found this on the hosted config-unterminated cell, passes that cell with this branch.

🤖 Generated with Claude Code

Hosted `scan` then `remove` left a newline at the end of a
`.cargo/config.toml` that had none. The rewriter appended two newlines
before its `[registries.socket-patch-<uuid>]` block there: one to end
the user's last line, one as a blank separator. `remove` takes back the
block and one newline. The edit record carried the extra newline for
the ledger-based revert older CLIs had; v5 hosted mode keeps no ledger,
and the file itself cannot tell that config from one that ended in a
newline, so the extra one stayed.

The rewriter now puts exactly one newline before the block whatever the
config ends with. After a final newline that is the blank separator, as
before. After none it is the line ending the config lacked, and the
block follows the last line directly. remove_registry_block already
inverts that form byte for byte and is unchanged.

A config that 5.1.0 or earlier already hosted still comes back with the
final newline added: nothing in the file says which form it had.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
e2e_redirect_cargo_shapes let a file with no final newline come back
with one added, because the rewriter could not help it. With one
newline before the appended registry block the restore is exact, so
the check now compares every file byte for byte.
cargo_hosted_config_trailing_bytes_are_restored covers the
`config-unterminated` shape with real cargo.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
From /code-review high on the PR.

With the block straight after a config's last line, content the user
later put directly after the block was glued to that line on `remove`
(`retry = 3[build]`), which cargo cannot parse. That was new with the
previous commit. The newline before the block now stays when taking it
would join two lines.

Three older defects in the same function are fixed with it:

- A config with mixed line endings came back all CRLF. Only a file
  whose every line ends in CRLF is converted, the rewriter's own rule.
- A comment heading the next section was deleted with the block.
  Blank lines and comments after the block's last key are the user's
  and stay.
- The block was found by searching for its text, which could match
  inside another line. It is located by its header line.

remove_appended_cargo_block is folded into remove_registry_block; its
CRLF branches were unreachable from there.

A new golden, redirect/cargo/cargo/config-unterminated, pins the append
for a config with no final newline in the rewrite, restore and vex
discovery goldens. restore_gives_back_a_config_with_no_final_newline
takes its hosted bytes from the rewriter, so it no longer passes
without the fix.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 14, 2026 04:31
@cursor

cursor Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes hosted Cargo config rewrite/revert contract and registry-block removal logic; incorrect edge-case handling could corrupt configs or break remove round-trips, but scope is localized to redirect restore paths with extensive new tests.

Overview
Hosted remove now restores .cargo/config.toml byte-for-byte, including configs that lack a trailing newline, by aligning rewrite and revert on a single separator newline.

plan_cargo_config always appends "\n" + [registries.…] (no extra blank line when the file ended without \n), and records only the registry block in edits—not the leading newline—so v5 hosted mode can invert the change from the file alone.

remove_registry_block is rewritten to strip that block plus exactly one preceding newline (line-based bounds, CRLF/mixed endings, trailing comments/sections, and cases where dropping the newline would glue the prior line to following content).

E2E config-unterminated / legacy-config expectations are tightened (no tolerance for an added final newline), with new unit/golden coverage in config-unterminated fixtures and cargo_config_append_is_undone_byte_for_byte.

Reviewed by Cursor Bugbot for commit 2c6cb5d. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] PR shepherd: review brief for the approver

What it does. Hosted scan followed by remove now gives back a .cargo/config.toml with no final newline byte for byte. Before, remove left an extra newline at the end. The rewriter now puts exactly one newline before its [registries.socket-patch-…] block whatever the config ends with, and remove_registry_block was rewritten to work line by line and take back that one newline. It also keeps comments and content the user added after the block, and it leaves files with mixed line endings as they are.

Risk. Low-medium. Only hosted Cargo config files change. In a config with no final newline the block now follows the last line directly, with no blank line in between (still valid TOML). Configs hosted by an earlier release still get a final newline added on remove, as the description says.

Look here.

  • redirect/mod.rs:4493: the append now always uses one newline before the block
  • [upstream/cargo.rs:463](https://github.com/SocketDev/socket-patch/blob/2c6cb5d0838b52222ef66038f35ae156a3077353/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs#L463): the new remove_registry_block
  • [upstream/cargo.rs:473](https://github.com/SocketDev/socket-patch/blob/2c6cb5d0838b52222ef66038f35ae156a3077353/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs#L473): trailing blank lines and comments after the block now stay with the user's content
  • [upstream/cargo.rs:491](https://github.com/SocketDev/socket-patch/blob/2c6cb5d0838b52222ef66038f35ae156a3077353/crates/socket-patch-core/src/patch/redirect/upstream/cargo.rs#L491): keeps the newline when dropping it would join two lines (retry = 3[build])
  • [e2e_redirect_cargo_shapes.rs:759](https://github.com/SocketDev/socket-patch/blob/2c6cb5d0838b52222ef66038f35ae156a3077353/crates/socket-patch-cli/tests/e2e_redirect_cargo_shapes.rs#L759): the e2e test no longer tolerates an added final newline

Verified. I read the whole diff and traced the byte arithmetic in remove_registry_block for these cases: a block at the end of the file with and without a final newline, a block at the start of an empty config, all-CRLF files, and mixed CRLF/LF files, where crlf_to_lf returns None and \r stays on each line. The edit record's new is only compared by goldens, and no revert reads it, so dropping the extra newline from it is safe. Round-trip unit tests cover six configs plus two blocks removed in either order. A new config-unterminated fixture feeds the rewrite, restore and vex goldens. CI is green on 2c6cb5d083 (ci-ok, clippy). No open review threads. CHANGELOG.md is untouched.

Changes I made. None.

Open questions. None blocking. A cross-repo follow-up: depscan's TypeScript rewriter has the same two-newline append, so its parity test will fail on the next socket-patch bump unless it follows or the case goes in TS_LAGGING.


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant