Repository navigation
Fix vendored uv export requirements.txt (#1368) - #1390
Mikola Lysenko (mikolalysenko) wants to merge 6 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A uv project often keeps a requirements.txt made by `uv export` for Docker or plain pip installs. Vendored mode wired only uv.lock and named the export as an unpatched install source, so `vendor --check` and `vex` reported contested wiring, and re-running vendor changed nothing, so the suggested remedy looped. Vendoring a uv project now also rewrites an exact registry pin of the package in requirements.txt (or an in-root -r include) to the same committed wheel, as already done beside Pipfile.lock (#612). The ledger entry records both, revert restores both, a superseding patch re-wires both, and a re-run over an already wired uv.lock wires an export made since. Pins the requirements wiring cannot rewrite (a range, a UTF-16 file) stay named by pypi_multiple_lockfiles. The Pipenv and uv paths now share one helper for the sibling wiring and one for its revert. Refs #1368 Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
- A symlinked requirements.txt or -r include can't be rewritten in place, so it is no longer treated as wirable. Those projects vendor the lock and name the export as before, instead of failing the run. - A hosted takeover of a uv or Pipenv entry whose export pin sits in a -r include is refused before the revert, as it already is for a requirements-flavor entry, since hosted mode re-pins only the root requirements.txt. - A fresh --dry-run now previews the export wiring. - Rollback after a refused export reuses restore_snapshot, so a file that can't be put back is named in the error. - The flavor matches for the sibling lock and its revert are exhaustive, and the doc comments no longer say Pipenv only. Refs #1368 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
- revert_with_sibling reverted the export, then let a drift-kept lock revert (uv's pair gate writes neither file) leave uv.lock wired. The export is now put back on the wheel too, matching the kept entry, so a re-run reverts both once the drift is undone. - snapshot_files recorded an unreadable file as absent, so a rollback could delete it. Only NotFound is recorded as absent now; anything else unreadable is left out and left alone. Refs #1368 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01645q8Fr6CEo5KdPqh3Ne7t
|
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 c1f669b. Configure here.
|
Ready for review at
Generated by Claude Code |
Final review briefWhat it does. Generalises the Pipenv "sibling requirements.txt" co-wiring (#612/#1309) to any lock flavor and turns it on for uv: detect, fresh vendor, in-sync re-run, supersede and revert all now handle a Risk: medium. It changes vendor, revert and detect on a common install path, and Pipenv moved too (shared helpers). It mirrors the proven Pipenv pattern, rollback is layered (uv pair restore, then the outer supersede snapshot), and test coverage is broad. Look here
Verified. Read the full diff and traced partial failure (export refusal restores the uv pair; supersede snapshot covers the export), path safety (ledger paths validated by Changes I made. None. Open questions (non-blocking).
Auto-merge is armed: approving sends it straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Refs #1368 (lane 2, the
uv exportrequirements.txt; lane 1, Pipenv'spylock.toml, is a follow-up slice, see below)Root cause
PR #1309 taught vendored Pipenv to co-wire an exact registry pin in an
exported
requirements.txtinto the same ledger entry asPipfile.lock.The uv flavor never got the same treatment: a
uv exportrequirements.txtbeside
uv.lockwas only named as an unpatched loser(
pypi_multiple_lockfiles).vendor --checkandvexthen reported thewiring as contested, and re-running vendor changed nothing, so the
suggested remedy looped. Hosted mode already rewrites both files.
Fix
The #1309 sibling wiring is now flavor-generic and shared by Pipenv and
uv:
detect_pypi_flavor: an exact, rewritable pin besideuv.lockis nolonger a loser (same predicate,
sibling_pin_wirable, as Pipenv).WiringPlan::Uvcarries a sibling flag; afterwire_uvthe export's pin goes to the same wheel. If that refuses, theuv pair is restored so neither file is left half wired
(
wire_sibling_requirements, shared with Pipenv).wire_in_sync_sibling(waswire_in_sync_pipenv_sibling) wires theexport to the verified committed wheel and replaces, never duplicates,
that file's records.
fresh plan re-detects the export and wires it to the new wheel.
revert_with_sibling(wasrevert_pipenv_with_sibling)reverts the export lines, then the lock records, restoring the export
if the lock revert fails. Dispatched for both
uvandpipenv.stay loud, as before.
CLI_CONTRACT.mdpypi_multiple_lockfilesrow updated.No wrapper (
npm/,pypi/,gem/) changes: this is core vendor logic.Tests (red on main, green here)
vendor::pypi::tests::uv_vendor_wires_the_exported_sibling_requirementsuv_in_sync_rerun_wires_a_later_export_onceuv_superseding_uuid_rewires_the_exportrequirements_beside_the_governing_lock_is_a_loud_loser(extended)vendor --checkexit 0, settled re-run,vendor --revertrestoresmode_migration_pypi::uv_requirements_export_is_wired_with_the_lockuv_symlinked_export_stays_a_loser--dry-runpreviews the export wiring, writes nothinguv_dry_run_previews_the_export_wiring-rincludepatch::redirect::pypi_takeover::tests::sibling_export_in_an_include_is_refuseduv_drifted_lock_keeps_the_export_wiredsnapshot_skips_unreadable_filesuv_requirements_export_contests_the_wiring_in_utf16(UTF-8 case moved to the test above)All four unit tests failed before the fix (
requirements.txt left unpatched, no entry on the in-sync re-run, export still on the olduuid) and pass after it.
mode_migration_pypi: 55/55 pass.cargo fmt --checkandcargo clippy --workspace --all-features -D warningsare clean.Local runs on 9a0f05f
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test --workspace --all-features --no-fail-fast: everything passes except 13 write-failure / unremovable-file tests in 5 targets (covgap_commands_vendor,e2e_bun_lockb,in_process_redirect,repair, core lib). The sandbox runs as uid 0, so chmod can't make those writes fail. I re-ran the same targets on unmodifiedorigin/mainand got the identical failure set, so they come from the environment, not this diff.e2e_vendor_pypi_build --include-ignoredwith uv 0.11.32 (SOCKET_PATCH_UV_E2E_REQUIRED=1): 52/52 pass./code-review high
All nine findings were handled in 9a0f05f except two. The double
sibling_pin_wirablewalk (once in detect, once in the prelude) is the same pattern Pipenv already has, and it costs one extra read of the small requirements tree. I left it alone so this PR stays scoped. The "per-flavor descriptor" suggestion is covered by exhaustive matches (sibling_lock,revert_lock) rather than a new type.Follow-up slice: Pipenv
use_pylock = true(#1368 lane 1)pylock.tomlbesidePipfile.lockneeds thepypi_lockbackendco-wired the same way (fresh / in-sync / in-place supersede / revert by
record kind). It's a different wiring backend, so it's left out of this
PR to keep it reviewable. #1368 stays open for it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01645q8Fr6CEo5KdPqh3Ne7t
Note
Medium Risk
Changes PyPI vendoring, revert, and lockfile discovery for a common Docker/pip install path; mistakes could leave half-wired locks or unpatched installs, but behavior mirrors existing Pipenv sibling wiring with rollback and extensive tests.
Overview
Vendored uv projects now co-wire a root
requirements.txtfromuv exportwithuv.lockto the same committed wheel—the same sibling pattern Pipenv already had forpipenv requirements(#612, #1368). That removes falsepypi_multiple_lockfiles/ contested wiring for rewritable exact pins, aligns vendored mode with hosted, and fixes the remedy loop where re-run vendor did nothing.Implementation generalizes Pipenv-only helpers: flavor detection skips the loser warning when
sibling_pin_wirablepasses foruv.lock; fresh vendoring runswire_uvthenwire_sibling_requirementswith rollback of the uv pair on refusal; in-sync re-runs usewire_in_sync_sibling; revert usesrevert_with_siblingfor both uv and pipenv. Hosted takeover preflight refuses exported pins wired only in-rincludes. Unrewritable exports (UTF-16, ranges, symlinked files) still warn or fail as before. CLI_CONTRACT and broad unit/CLI tests cover dry-run, supersede, drift-kept revert, and discovery.Reviewed by Cursor Bugbot for commit c1f669b. Configure here.
Generated by Claude Code