fork: preserve URLs in uv.lock across re-locks (v0.11.14)#2
Closed
harupy wants to merge 23 commits into
Closed
Conversation
When UV_DEFAULT_INDEX differs across environments (e.g. an internal mirror on developer machines vs. CI, or across regions), upstream `uv lock` rewrites `source.registry`, `sdist.url`, and `wheels[].url` in `uv.lock` to the current mirror's URLs, producing noisy diffs and breaking lockfile portability (astral-sh#6349). This fork adds `Lock::rewrite_urls_from`, invoked after `do_lock` in `LockOperation::execute`, which copies URL fields from the previous lockfile onto the newly-resolved lock whenever a package is still present at the same (name, version). Hashes and versions continue to be written as resolved — only URL fields are held stable. Non-registry sources (git, direct URL, path, directory, editable, virtual) are left untouched. Version bumps naturally pick up fresh URLs because the (name, version) key no longer matches. Also adds a `Fork release` GitHub Actions workflow that publishes a rolling `fork-latest` release with linux x86_64 and macOS aarch64 binaries on every push to main. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Workflow now triggers on pull_request (opened/synchronize/reopened) and publishes a release tagged `pr-<number>` per PR, so each standing fork PR gets its own binaries without overwriting other PRs' assets. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously each run deleted the existing release (and its tag) before recreating it, which briefly left no release present. Now the tag ref is force-updated via the GitHub API, release notes are edited in place, and assets are replaced with `gh release upload --clobber`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin all actions to the same SHAs upstream uv uses: - actions/checkout v6.0.2 - Swatinem/rust-cache v2.9.1 - actions/upload-artifact v7.0.1 - actions/download-artifact v8.0.1 Add a per-PR concurrency group so a new push to a PR cancels any in-progress run for the same PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the only difference between the resolver's new lock and the previous on-disk lock is the index URL (and derived file URLs), `rewrite_urls_from` produces a new lock equal to the previous one. Before this change we still wrote that byte-identical content back to disk, touching the file's mtime and bothering build tools that watch for modifications. Now we compare the post-rewrite lock against `previous` and skip `target.commit(...)` when they match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`PackageId` implements Hash+Eq over its `source` field. Mutating `Package.id.source` without touching `Dependency.package_id.source` left the same logical package keyed under two different hashes, causing a panic in `installable.rs:508` when `inverse[&package.id]` could not find the entry that had been inserted under the dependency's hash. Fix by applying the preserved-registry URL substitution to every `PackageId` occurrence in the lock — `Package.id` plus every `Dependency.package_id` inside `dependencies`, `optional_dependencies`, and `dependency_groups`. Added a two-package dependency-edge test that verifies all `Dependency.package_id.source` values are rewritten and that the re-serialized lock is byte-identical to the previous one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Lock.by_id` is a cached `FxHashMap<PackageId, usize>` built once in `Lock::new`. After `rewrite_urls_from` mutates `Package.id.source`, the cached keys become stale and `find_by_id` panics at `lock/mod.rs:1464` with "locked package for ID" during sync/graph construction (for example via `installable.rs:2223`). Rebuild `by_id` at the end of `rewrite_urls_from` to reflect the post-mutation PackageIds. Add a regression test that runs `find_by_id` on every dependency after a rewrite. Also update the release-notes install snippets to use `mktemp -d` + `trap` cleanup so the downloaded tarball and extracted binaries are removed after installation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL preservation keeps the previous registry URL in the lockfile, which won't match the current UV_DEFAULT_INDEX when mirrors differ. The satisfies() check was returning MissingRemoteIndex on every run, forcing a full re-resolve even when nothing changed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `uv add` introduces a new package, it gets the current UV_DEFAULT_INDEX mirror URL as its registry source. Previously, rewrite_urls_from only preserved URLs for packages matching by (name, version) — new packages kept the mirror URL. Now infer the canonical registry URL from the previous lockfile and apply it to all registry packages, including newly added ones, so the lockfile stays consistent regardless of which mirror resolved the package. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the complex "infer canonical URL from previous lockfile" logic with a simple env var mapping. UV_PYPI_PROXIES maps canonical URLs to proxy URLs (format: canonical:proxy,canonical2:proxy2). After resolution, rewrite_proxy_urls() replaces every matching proxy registry URL with its canonical counterpart. This handles both existing and newly added packages uniformly — no need to track the previous lockfile at all. Example: UV_PYPI_PROXIES=https://pypi.org/simple:https://pypi-proxy.dev.databricks.com/simple Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of commenting out the entire remotes collection and check in satisfies(), restore the original upstream code and just add canonical URLs from UV_PYPI_PROXIES to the remotes set. This reduces the mod.rs diff from ~60 lines of comments to 8 added lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UrlString doesn't implement From<&str>; use UrlString::new(SmallString::from(...)) to construct from string slices. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When satisfies() returns Satisfied (lockfile already up-to-date), the result is Unchanged and the rewrite_proxy_urls code path was skipped. Now handle both Changed and Unchanged cases so proxy URLs get rewritten to canonical even when no re-resolution was needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lockfile stores canonical URLs (e.g. pypi.org/simple), but uv sync needs to fetch from the proxy that is actually reachable. Add proxy_url() reverse mapping and apply it at the 3 points where RegistrySource::Url is converted to IndexUrl for HTTP fetching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Log when rewriting proxy→canonical (lock) and canonical→proxy (sync) so the mapping is visible in verbose mode (`uv -v`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no explicit default index is configured, uv falls back to pypi.org/simple. If UV_INDEX_PROXIES maps pypi.org to a proxy, the default index now resolves through the proxy so that build-system.requires (e.g. setuptools) can be fetched without direct pypi.org access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…or summary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous fix only patched IndexLocations::default_index(), but the resolver uses IndexUrls::default_index() which had a separate fallback to the hardcoded PyPI URL. Apply the same proxy mapping here so that uv lock also fetches through the proxy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rewrite proxy registry URLs in
uv.lockto their canonical counterparts via theUV_INDEX_PROXIESenvironment variable. Rebased on top of upstream0.11.14.Context: astral-sh#6349. When
UV_DEFAULT_INDEXpoints at an internal mirror, upstreamuv lockrewrites everysource.registryURL inuv.lockto the mirror, creating noisy diffs and breaking portability across environments using different mirrors.How it works
Set
UV_INDEX_PROXIESwithcanonical:proxymappings:export UV_INDEX_PROXIES=https://pypi.org/simple:https://pypi-proxy.example.com/simpleAfter resolution,
Lock::rewrite_proxy_urls()replaces every matching proxy registry URL with its canonical counterpart in the lockfile. The canonical URLs are also injected into thesatisfies()check so that subsequentuv lockruns recognize the lockfile as up-to-date (no unnecessary re-resolve).Changes vs. prior PR (#1)
0.11.14.UV_PYPI_PROXIES→UV_INDEX_PROXIES.IndexUrls::default_index()).uv syncso installs hit the mirror.Unchangedlock results (no-resolve fast path).trace!per-package,debug!for summary.All fork changes are tagged with
// fork:comments so rebasers can find them quickly.Test plan
cargo test -p uv-resolver --lib url_preservationpasses locally.uv lockrun reportsExisting uv.lock satisfies workspace requirements(no re-resolve).🤖 Generated with Claude Code