Treat --upgrade-package on the command-line as overriding upgrade = false in configuration (#15395)

## Summary

Right now, if you put `upgrade = false` in a `uv.toml`, then pass
`--upgrade-package numpy` on the CLI, we won't upgrade NumPy. This PR
fixes that interaction by ensuring that when we "combine", we look at
those arguments holistically (i.e., we bundle `upgrade` and
`upgrade-package` into a single struct, which then goes through the
`.combine` logic), rather than combining `upgrade` and `upgrade-package`
independently.

If approved, I then need to add the same thing for `no-build-isolation`,
`reinstall`, `no-build`, and `no-binary`.
This commit is contained in:
Charlie Marsh 2025-08-21 16:20:55 +01:00 committed by GitHub
parent b950453891
commit 0397595e53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 357 additions and 145 deletions

View file

@ -134,9 +134,57 @@ impl From<Reinstall> for Refresh {
}
}
/// An upgrade selection as specified by a user on the command line or in a configuration file.
#[derive(Debug, Default, Clone)]
pub enum UpgradeSelection {
/// Prefer pinned versions from the existing lockfile, if possible.
#[default]
None,
/// Allow package upgrades for all packages, ignoring the existing lockfile.
All,
/// Allow package upgrades, but only for the specified packages.
Packages(Vec<Requirement>),
}
impl UpgradeSelection {
/// Determine the upgrade selection strategy from the command-line arguments.
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<Requirement>) -> Option<Self> {
match upgrade {
Some(true) => Some(Self::All),
// TODO(charlie): `--no-upgrade` with `--upgrade-package` should allow the specified
// packages to be upgraded. Right now, `--upgrade-package` is silently ignored.
Some(false) => Some(Self::None),
None if upgrade_package.is_empty() => None,
None => Some(Self::Packages(upgrade_package)),
}
}
/// Combine a set of [`UpgradeSelection`] values.
#[must_use]
pub fn combine(self, other: Self) -> Self {
match self {
// Setting `--upgrade` or `--no-upgrade` should clear previous `--upgrade-package` selections.
Self::All | Self::None => self,
Self::Packages(self_packages) => match other {
// If `--upgrade` was enabled previously, `--upgrade-package` is subsumed by upgrading all packages.
Self::All => other,
// If `--no-upgrade` was enabled previously, then `--upgrade-package` enables an explicit upgrade of those packages.
Self::None => Self::Packages(self_packages),
// If `--upgrade-package` was included twice, combine the requirements.
Self::Packages(other_packages) => {
let mut combined = self_packages;
combined.extend(other_packages);
Self::Packages(combined)
}
},
}
}
}
/// Whether to allow package upgrades.
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[derive(Debug, Default, Clone)]
pub enum Upgrade {
/// Prefer pinned versions from the existing lockfile, if possible.
#[default]
@ -149,30 +197,28 @@ pub enum Upgrade {
Packages(FxHashMap<PackageName, Vec<Requirement>>),
}
impl Upgrade {
/// Determine the [`Upgrade`] strategy from the command-line arguments.
pub fn from_args(upgrade: Option<bool>, upgrade_package: Vec<Requirement>) -> Self {
match upgrade {
Some(true) => Self::All,
Some(false) => Self::None,
None => {
if upgrade_package.is_empty() {
Self::None
} else {
Self::Packages(upgrade_package.into_iter().fold(
FxHashMap::default(),
|mut map, requirement| {
map.entry(requirement.name.clone())
.or_default()
.push(requirement);
map
},
))
}
}
/// Determine the [`Upgrade`] strategy from the command-line arguments.
impl From<Option<UpgradeSelection>> for Upgrade {
fn from(value: Option<UpgradeSelection>) -> Self {
match value {
None => Self::None,
Some(UpgradeSelection::None) => Self::None,
Some(UpgradeSelection::All) => Self::All,
Some(UpgradeSelection::Packages(requirements)) => Self::Packages(
requirements
.into_iter()
.fold(FxHashMap::default(), |mut map, requirement| {
map.entry(requirement.name.clone())
.or_default()
.push(requirement);
map
}),
),
}
}
}
impl Upgrade {
/// Create an [`Upgrade`] strategy to upgrade a single package.
pub fn package(package_name: PackageName) -> Self {
Self::Packages({