Fix uv sources unwind on dotted/sub-table spellings (#544, #524) - #545
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 |
|
[agent] Ready for review at Generated by Claude Code |
|
[agent] Labeled Ready for review at Generated by Claude Code |
|
Reviewed No actionable correctness/security regressions found in the rendered-line ledger recording or implicit-header cleanup. Normalizing both sides with rustfmt confirms the surrounding 124-file churn is formatting-only; reviewed the four semantic files. Validation: core |
An earlier cargo fmt --all run reformatted 124 files this fix doesn't touch. Restore them to main so the PR only carries the uv sources change and its tests. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Thanks for the review. The 124 files of formatting-only churn it flagged came from my 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 f1a45b0. Configure here.
|
Final incremental review of Validation carried forward: 130 uv unit tests, 8 TOML surgery tests, 3 real-uv tests, plus #543’s 55 discovery tests and 33 vendor rescan tests passed on the reviewed component heads. These were not rerun for the final formatting/merge-only update. The failed PDM CI job is blocked by an external patch API HTTP 504, confirmed in its uploaded scan output. Recommendation: merge once the failed CI check has been rerun successfully. |
|
[agent] 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.An earlier
cargo fmt --allhad also reformatted 124 unrelated files (mainisn't rustfmt-clean, and CI has no fmt check). Those are reverted atf1a45b0, so the diff is just the 4 files above.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 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. Re-run onf1a45b0: 23/23 pass.cargo test -p socket-patch-cli --all-features --test e2e_redirect_uv_build -- --include-ignored(uv 0.8.17): 16/16 pass (also onf1a45b0).🤖 Generated with Claude Code
https://claude.ai/code/session_013Lodu4CMs7Cfqq8pEzPXpb
Note
Medium Risk
Changes pyproject/uv.lock revert and hosted rollback logic for dependency wiring; mistakes could leave projects pointed at vendored wheels or corrupt TOML layout, though heavily regression-tested.
Overview
Fixes vendor revert and hosted metadata restore for uv projects that express
[tool.uv]sources with dotted keys or sub-table-only parents (#544, #524).Vendored path: wiring now records each added
pyproject.tomlline exactly astoml_editrendered it (e.g.sources.six = …vssix = …), so revert splices match and no longer flag socket-patch’s own line as drift. Implicit parents that only existed via[tool.uv.sources.<pkg>]sub-tables are treated as “ours,” so revert also drops headers the wire step introduced.Hosted restore: after removing hosted source entries,
[tool.uv.sources]/[tool.uv]are made implicit again when they only contain sub-tables, restoring the original sub-table-only spelling.TOML surgery:
remove_table_if_emptyno longer strips blank lines that belong to the next section header (fixes spacing when removing an empty[tool.uv.sources]above sub-tables).Coverage adds unit tests across
pypi_uv, upstreamuvrestore, andtoml_surgery, plus three real-uv e2e round-trip tests (dotted keys, dotted url key, sub-table sources).Reviewed by Cursor Bugbot for commit f1a45b0. Configure here.
Generated by Claude Code