Fix uv sources unwind on dotted/sub-table spellings (#544, #524) - #545
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Vendored revert and remove now find the sources and override lines they wrote even when the project spells [tool.uv] sources as dotted keys, so a revert no longer reports its own line as drift and leaves pyproject.toml routed to the vendored wheel while uv.lock is restored (which broke uv sync --locked). (#544) When a project only has [tool.uv.sources.<pkg>] sub-tables, the scan has to print an explicit [tool.uv.sources] header. Vendored revert and hosted rollback/remove now drop that header again, so an unwind leaves pyproject.toml byte-identical. (#524) Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 bec2311. Configure here.
|
[agent] Ready for review at Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #544
Fixes #524
Summary
After vendoring or a hosted scan, uv projects whose existing
[tool.uv]sources aren't written as a plain[tool.uv.sources]table now unwind cleanly:sources.<pkg>line stays, souv sync --lockedfails (vendor --revert exits 0) #544):vendor --revertandremoveno longer report socket-patch's own sources line as "drift". Before, they restoreduv.lockbut leftpyproject.tomlrouted to the vendored wheel, which brokeuv sync --lockedand kept six silently patched on a plainuv sync. Affected spellings:[tool.uv]+sources.x = {…},sources.x.path = …,[tool]+uv.sources.x = …, and a roottool.uv.sources.x = …. The same bug hit the transitiveuv.override-dependencies = […]line.[tool.uv.sources]header behind when the project's sources are written as[tool.uv.sources.<name>]sub-tables #524): vendored revert and hosted rollback/remove now drop the[tool.uv.sources]header (and, for transitive deps, the[tool.uv]header) that the scan had to make explicit, so the round trip is byte-identical.Root cause
The uv wiring assumed sources live under an explicit
[tool.uv.sources]header asname = {…}lines. toml_edit, though, writes a new key in the spelling its parent already has:six = { path = … }in the ledger, while the file heldsources.six = { … }. Revert's exact-line splice missed it, and the fragment was treated as user drift.[tool.uv.sources.<pkg>]sub-tables) prints its header once a key is added. Vendored mode decided "we didn't create the table" because the table existed, and the hosted restore left it explicit.Changes
vendor/pypi_uv.rs: record each added pyproject line exactly as it rendered, dotted prefix included (rendered_key_line). Treat a header-less implicit parent as ours (header_is_ours), so revert removes the header it printed.vendor/toml_surgery.rs:remove_table_if_emptykeeps the blank lines before a following header, because they are that table's own separator. Previously it ate the sub-table's spacing. The pinned helper test was updated, and a uv rollback, remove and vendor --revert leave an empty[tool.uv.sources]header behind when the project's sources are written as[tool.uv.sources.<name>]sub-tables #524-shaped case added.patch/redirect/upstream/uv.rs:restore_metadatamakes[tool.uv.sources]/[tool.uv]implicit again once only sub-tables remain (hide_header_over_sub_tables).Known trade-off: a user who wrote an explicit, key-less
[tool.uv.sources]header with only sub-tables beneath it gets that header dropped by a hosted rollback. The hosted path has no ledger, so it can't tell that header apart from one the scan made explicit. The result still parses identically, and that shape is unusual.No wrapper changes are needed (
npm/,pypi/,gem/only dispatch to the binary).Test evidence
Each regression test was run red on the pre-fix code and green with the fix:
vendor::pypi_uv::tests::revert_round_trips_dotted_sources_under_tool_uv…revert_round_trips_dotted_sources_path_key…revert_round_trips_dotted_uv_sources_under_tool(follow-up comment's[tool]+uv.sources)…revert_round_trips_root_dotted_tool_uv_sources…revert_round_trips_dotted_sources_crlf…revert_round_trips_dotted_override_under_tool(transitive)e2e_vendor_pypi_build::uv_vendor_revert_dotted_sources_key,…_dotted_sources_url_key(real uv 0.8.17, plusuv lock --checkafter revert)vendor_lock_entry_drifted…revert_drops_header_made_explicit_over_sub_tables(+_override,_crlf)patch::redirect::upstream::uv::tests::restore_drops_sources_header_made_explicit_over_sub_tables(+_transitive,_crlf)e2e_vendor_pypi_build::uv_vendor_revert_sub_table_sources(real uv 0.8.17)[tool.uv.sources]\n\nresiduerevert_keeps_user_authored_sources_header,restore_keeps_user_sources_spellingsCommands run locally on
bec2311:cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features --no-fail-fast: all pass except 12 permission-injection tests (chmod/set_permissionsread-only fixtures incovgap_commands_vendor,in_process_redirect,repair, and corecopy_tree/vlt_heal/pypi_poetry/pypi_requirements). Those can't fail as intended because the sandbox runs as root. None of them is in a file this PR touches, and CI runs as non-root.cargo test -p socket-patch-cli --all-features --test e2e_vendor_pypi_build -- --include-ignored(uv 0.8.17): every uv test passes, including the 3 new ones. Two pip/requirements tests (pip_requirements_vendor_fresh_checkout_no_index_and_revert,pip_vendored_requirements_evaluate_environment_markers) panic in the harness'scopy_treein this sandbox. They exercise code this PR doesn't touch, so CI is authoritative.cargo test -p socket-patch-cli --all-features --test e2e_redirect_uv_build -- --include-ignored(uv 0.8.17): 16/16 pass.🤖 Generated with Claude Code
https://claude.ai/code/session_013Lodu4CMs7Cfqq8pEzPXpb
Note
Medium Risk
Changes how vendor and hosted flows edit
pyproject.tomlanduv.lockfor uv projects; mistakes could break lock consistency or mis-remove user TOML, though scope is limited to unwind/restore paths with new regression tests.Overview
Fixes uv vendor revert and hosted unwind when
pyproject.tomlalready expresses[tool.uv]sources with dotted keys or sub-table-only layouts (#544, #524), sovendor --revert/ rollback no longer leave falsevendor_lock_entry_driftedwarnings or stray[tool.uv.sources]headers and the project returns byte-identicalpyproject.toml/uv.lockthatuv lock --checkaccepts.Core work records the exact line
toml_editemitted when wiring (rendered_key_line), treats implicit parent tables as owned so revert can drop headers the scan made explicit (header_is_ours), tightens TOML removal spacing intoml_surgery, and mirrors the same cleanup on the hosted restore path (hide_header_over_sub_tables).This diff also adds real-uv e2e capstone tests for three source spellings and applies rustfmt across
socket-patch-cli(formatting only).Reviewed by Cursor Bugbot for commit bec2311. Configure here.
Generated by Claude Code