Fix uv sync --no-sources not switching from editable to registry installations (#15234)

## Summary

Fixes issue #15190 where `uv sync --no-sources` fails to switch from
editable to registry package installations. The problem occurred because
the installer's satisfaction check didn't consider the `--no-sources`
flag when determining if an existing editable installation was
compatible with a registry requirement.

## Solution

Modified `RequirementSatisfaction::check()` to reject non-registry
installations when `SourceStrategy::Disabled` and the requirement is
from registry. Added `SourceStrategy` parameter threading through the
entire call chain from commands to the satisfaction check to ensure
consistent behavior between `uv sync --no-sources` and `uv pip install
--no-sources`.

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
chisato 2025-09-17 19:35:32 +08:00 committed by GitHub
parent eb5ec95396
commit accfb48876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 271 additions and 17 deletions

View file

@ -6,8 +6,10 @@ use pubgrub::Range;
use smallvec::SmallVec;
use tracing::{debug, trace};
use uv_configuration::IndexStrategy;
use uv_distribution_types::{CompatibleDist, IncompatibleDist, IncompatibleSource, IndexUrl};
use uv_configuration::{IndexStrategy, SourceStrategy};
use uv_distribution_types::{
CompatibleDist, IncompatibleDist, IncompatibleSource, IndexUrl, InstalledDistKind,
};
use uv_distribution_types::{DistributionMetadata, IncompatibleWheel, Name, PrioritizedDist};
use uv_normalize::PackageName;
use uv_pep440::Version;
@ -26,6 +28,7 @@ pub(crate) struct CandidateSelector {
resolution_strategy: ResolutionStrategy,
prerelease_strategy: PrereleaseStrategy,
index_strategy: IndexStrategy,
source_strategy: SourceStrategy,
}
impl CandidateSelector {
@ -34,6 +37,7 @@ impl CandidateSelector {
options: &Options,
manifest: &Manifest,
env: &ResolverEnvironment,
source_strategy: SourceStrategy,
) -> Self {
Self {
resolution_strategy: ResolutionStrategy::from_mode(
@ -49,6 +53,7 @@ impl CandidateSelector {
options.dependency_mode,
),
index_strategy: options.index_strategy,
source_strategy,
}
}
@ -119,7 +124,13 @@ impl CandidateSelector {
let installed = if reinstall {
None
} else {
Self::get_installed(package_name, range, installed_packages, tags)
Self::get_installed(
package_name,
range,
installed_packages,
tags,
self.source_strategy,
)
};
// If we're not upgrading, we should prefer the already-installed distribution.
@ -369,6 +380,7 @@ impl CandidateSelector {
range: &Range<Version>,
installed_packages: &'a InstalledPackages,
tags: Option<&'a Tags>,
source_strategy: SourceStrategy,
) -> Option<Candidate<'a>> {
let installed_dists = installed_packages.get_packages(package_name);
match installed_dists.as_slice() {
@ -381,6 +393,16 @@ impl CandidateSelector {
return None;
}
// When sources are disabled, only allow registry installations to be reused
if matches!(source_strategy, SourceStrategy::Disabled) {
if !matches!(dist.kind, InstalledDistKind::Registry(_)) {
debug!(
"Source strategy is disabled, rejecting non-registry installed distribution: {dist}"
);
return None;
}
}
// Verify that the installed distribution is compatible with the environment.
if tags.is_some_and(|tags| {
let Ok(Some(wheel_tags)) = dist.read_tags() else {

View file

@ -20,7 +20,7 @@ use tokio::sync::oneshot;
use tokio_stream::wrappers::ReceiverStream;
use tracing::{Level, debug, info, instrument, trace, warn};
use uv_configuration::{Constraints, Overrides};
use uv_configuration::{Constraints, Overrides, SourceStrategy};
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution_types::{
BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
@ -36,6 +36,7 @@ use uv_pep508::{
};
use uv_platform_tags::Tags;
use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
use uv_torch::TorchStrategy;
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
use uv_warnings::warn_user_once;
@ -82,7 +83,6 @@ use crate::{
marker,
};
pub(crate) use provider::MetadataUnavailable;
use uv_torch::TorchStrategy;
mod availability;
mod batch_prefetch;
@ -201,6 +201,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
build_context.git(),
build_context.capabilities(),
build_context.locations(),
build_context.sources(),
provider,
installed_packages,
)
@ -224,6 +225,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
git: &GitResolver,
capabilities: &IndexCapabilities,
locations: &IndexLocations,
source_strategy: SourceStrategy,
provider: Provider,
installed_packages: InstalledPackages,
) -> Result<Self, ResolveError> {
@ -231,7 +233,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
index: index.clone(),
git: git.clone(),
capabilities: capabilities.clone(),
selector: CandidateSelector::for_resolution(&options, &manifest, &env),
selector: CandidateSelector::for_resolution(&options, &manifest, &env, source_strategy),
dependency_mode: options.dependency_mode,
urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),