diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 9f02490c6..3e9aba123 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -532,8 +532,10 @@ pub struct VersionArgs { pub value: Option, /// Update the project version using the given semantics + /// + /// This flag can be passed multiple times. #[arg(group = "operation", long)] - pub bump: Option, + pub bump: Vec, /// Don't write a new version to the `pyproject.toml` /// @@ -608,14 +610,56 @@ pub struct VersionArgs { pub python: Option>, } -#[derive(Debug, Copy, Clone, PartialEq, clap::ValueEnum)] +// Note that the ordering of the variants is significant, as when given a list of operations +// to perform, we sort them and apply them in order, so users don't have to think too hard about it. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)] pub enum VersionBump { - /// Increase the major version (1.2.3 => 2.0.0) + /// Increase the major version (e.g., 1.2.3 => 2.0.0) Major, - /// Increase the minor version (1.2.3 => 1.3.0) + /// Increase the minor version (e.g., 1.2.3 => 1.3.0) Minor, - /// Increase the patch version (1.2.3 => 1.2.4) + /// Increase the patch version (e.g., 1.2.3 => 1.2.4) Patch, + /// Move from a pre-release to stable version (e.g., 1.2.3b4.post5.dev6 => 1.2.3) + /// + /// Removes all pre-release components, but will not remove "local" components. + Stable, + /// Increase the alpha version (e.g., 1.2.3a4 => 1.2.3a5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0a1, you'd also include [`VersionBump::Major`]. + Alpha, + /// Increase the beta version (e.g., 1.2.3b4 => 1.2.3b5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0b1, you'd also include [`VersionBump::Major`]. + Beta, + /// Increase the rc version (e.g., 1.2.3rc4 => 1.2.3rc5) + /// + /// To move from a stable to a pre-release version, combine this with a stable component, e.g., + /// for 1.2.3 => 2.0.0rc1, you'd also include [`VersionBump::Major`].] + Rc, + /// Increase the post version (e.g., 1.2.3.post5 => 1.2.3.post6) + Post, + /// Increase the dev version (e.g., 1.2.3a4.dev6 => 1.2.3.dev7) + Dev, +} + +impl std::fmt::Display for VersionBump { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = match self { + VersionBump::Major => "major", + VersionBump::Minor => "minor", + VersionBump::Patch => "patch", + VersionBump::Stable => "stable", + VersionBump::Alpha => "alpha", + VersionBump::Beta => "beta", + VersionBump::Rc => "rc", + VersionBump::Post => "post", + VersionBump::Dev => "dev", + }; + string.fmt(f) + } } #[derive(Args)] diff --git a/crates/uv-pep440/src/lib.rs b/crates/uv-pep440/src/lib.rs index 0e8b50e72..40c7d97c6 100644 --- a/crates/uv-pep440/src/lib.rs +++ b/crates/uv-pep440/src/lib.rs @@ -29,8 +29,8 @@ pub use version_ranges::{ }; pub use { version::{ - LocalSegment, LocalVersion, LocalVersionSlice, MIN_VERSION, Operator, OperatorParseError, - Prerelease, PrereleaseKind, Version, VersionParseError, VersionPattern, + BumpCommand, LocalSegment, LocalVersion, LocalVersionSlice, MIN_VERSION, Operator, + OperatorParseError, Prerelease, PrereleaseKind, Version, VersionParseError, VersionPattern, VersionPatternParseError, }, version_specifier::{ diff --git a/crates/uv-pep440/src/version.rs b/crates/uv-pep440/src/version.rs index a496f95a2..223701692 100644 --- a/crates/uv-pep440/src/version.rs +++ b/crates/uv-pep440/src/version.rs @@ -643,6 +643,90 @@ impl Version { self.with_release(release) } + /// Various "increment the version" operations + pub fn bump(&mut self, bump: BumpCommand) { + // This code operates on the understanding that the components of a version form + // the following hierarchy: + // + // major > minor > patch > stable > pre > post > dev + // + // Any updates to something earlier in the hierarchy should clear all values lower + // in the hierarchy. So for instance: + // + // if you bump `minor`, then clear: patch, pre, post, dev + // if you bump `pre`, then clear: post, dev + // + // ...and so on. + // + // If you bump a value that doesn't exist, it will be set to "1". + // + // The special "stable" mode has no value, bumping it clears: pre, post, dev. + let full = self.make_full(); + + match bump { + BumpCommand::BumpRelease { index } => { + // Clear all sub-release items + full.pre = None; + full.post = None; + full.dev = None; + + // Use `max` here to try to do 0.2 => 0.3 instead of 0.2 => 0.3.0 + let old_parts = &full.release; + let len = old_parts.len().max(index + 1); + let new_release_vec = (0..len) + .map(|i| match i.cmp(&index) { + // Everything before the bumped value is preserved (or is an implicit 0) + Ordering::Less => old_parts.get(i).copied().unwrap_or(0), + // This is the value to bump (could be implicit 0) + Ordering::Equal => old_parts.get(i).copied().unwrap_or(0) + 1, + // Everything after the bumped value becomes 0 + Ordering::Greater => 0, + }) + .collect::>(); + full.release = new_release_vec; + } + BumpCommand::MakeStable => { + // Clear all sub-release items + full.pre = None; + full.post = None; + full.dev = None; + } + BumpCommand::BumpPrerelease { kind } => { + // Clear all sub-prerelease items + full.post = None; + full.dev = None; + + // Either bump the matching kind or set to 1 + if let Some(prerelease) = &mut full.pre { + if prerelease.kind == kind { + prerelease.number += 1; + return; + } + } + full.pre = Some(Prerelease { kind, number: 1 }); + } + BumpCommand::BumpPost => { + // Clear sub-post items + full.dev = None; + + // Either bump or set to 1 + if let Some(post) = &mut full.post { + *post += 1; + } else { + full.post = Some(1); + } + } + BumpCommand::BumpDev => { + // Either bump or set to 1 + if let Some(dev) = &mut full.dev { + *dev += 1; + } else { + full.dev = Some(1); + } + } + } + } + /// Set the min-release component and return the updated version. /// /// The "min" component is internal-only, and does not exist in PEP 440. @@ -879,6 +963,27 @@ impl FromStr for Version { } } +/// Various ways to "bump" a version +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum BumpCommand { + /// Bump the release component + BumpRelease { + /// The release component to bump (0 is major, 1 is minor, 2 is patch) + index: usize, + }, + /// Bump the prerelease component + BumpPrerelease { + /// prerelease component to bump + kind: PrereleaseKind, + }, + /// Bump to the associated stable release + MakeStable, + /// Bump the post component + BumpPost, + /// Bump the dev component + BumpDev, +} + /// A small representation of a version. /// /// This representation is used for a (very common) subset of versions: the @@ -4043,4 +4148,351 @@ mod tests { assert_eq!(size_of::(), size_of::() * 2); assert_eq!(size_of::(), size_of::() * 2); } + + /// Test major bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_major() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0"); + + // three digit (zero major) + let mut version = "0.1.2".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "1.0.0"); + + // three digit (non-zero major) + let mut version = "1.2.3".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0.0"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "2.0.0.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "5!2.0.0.0+local"); + version.bump(BumpCommand::BumpRelease { index: 0 }); + assert_eq!(version.to_string().as_str(), "5!3.0.0.0+local"); + } + + /// Test minor bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_minor() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "0.1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "1.6"); + + // three digit (non-zero major) + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5.4.0"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "1.3.0.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5!1.8.0.0+local"); + version.bump(BumpCommand::BumpRelease { index: 1 }); + assert_eq!(version.to_string().as_str(), "5!1.9.0.0+local"); + } + + /// Test patch bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_patch() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "0.0.1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "1.5.1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5.3.7"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "1.2.4.0"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5!1.7.4.0+local"); + version.bump(BumpCommand::BumpRelease { index: 2 }); + assert_eq!(version.to_string().as_str(), "5!1.7.5.0+local"); + } + + /// Test alpha bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_alpha() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "0a1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "1.5a1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5.3.6a1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4a1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5a1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5a2+local"); + } + + /// Test beta bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_beta() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "0b1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "1.5b1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5.3.6b1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4b1"); + + // All the version junk + let mut version = "5!1.7.3.5a2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2+local"); + } + + /// Test rc bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_rc() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "0rc1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "1.5rc1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5.3.6rc1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "1.2.3.4rc1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345.dev456+local" + .parse::() + .unwrap(); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc1+local"); + version.bump(BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc2+local"); + } + + /// Test post bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_post() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "0.post1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "1.5.post1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5.3.6.post1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "1.2.3.4.post1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.dev123+local".parse::().unwrap(); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post1+local"); + version.bump(BumpCommand::BumpPost); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post2+local"); + } + + /// Test dev bumping + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn bump_dev() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "0.dev1"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "1.5.dev1"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "5.3.6.dev1"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!(version.to_string().as_str(), "1.2.3.4.dev1"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345+local".parse::().unwrap(); + version.bump(BumpCommand::BumpDev); + assert_eq!( + version.to_string().as_str(), + "5!1.7.3.5b2.post345.dev1+local" + ); + version.bump(BumpCommand::BumpDev); + assert_eq!( + version.to_string().as_str(), + "5!1.7.3.5b2.post345.dev2+local" + ); + } + + /// Test stable setting + /// Explicitly using the string display because we want to preserve formatting where possible! + #[test] + fn make_stable() { + // one digit + let mut version = "0".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "0"); + + // two digit + let mut version = "1.5".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "1.5"); + + // three digit + let mut version = "5.3.6".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5.3.6"); + + // four digit + let mut version = "1.2.3.4".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "1.2.3.4"); + + // All the version junk + let mut version = "5!1.7.3.5b2.post345+local".parse::().unwrap(); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local"); + version.bump(BumpCommand::MakeStable); + assert_eq!(version.to_string().as_str(), "5!1.7.3.5+local"); + } } diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index bc79f8eb9..ec278d4b4 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -1,6 +1,6 @@ use std::fmt::Write; +use std::path::Path; use std::str::FromStr; -use std::{cmp::Ordering, path::Path}; use anyhow::{Context, Result, anyhow}; use owo_colors::OwoColorize; @@ -15,7 +15,7 @@ use uv_configuration::{ }; use uv_fs::Simplified; use uv_normalize::DefaultExtras; -use uv_pep440::Version; +use uv_pep440::{BumpCommand, PrereleaseKind, Version}; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; @@ -55,7 +55,7 @@ pub(crate) fn self_version( #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn project_version( value: Option, - bump: Option, + mut bump: Vec, short: bool, output_format: VersionFormat, strict: bool, @@ -105,7 +105,7 @@ pub(crate) async fn project_version( }; // Short-circuit early for a frozen read - let is_read_only = value.is_none() && bump.is_none(); + let is_read_only = value.is_none() && bump.is_empty(); if frozen && is_read_only { return Box::pin(print_frozen_version( project, @@ -158,7 +158,8 @@ pub(crate) async fn project_version( match Version::from_str(&value) { Ok(version) => Some(version), Err(err) => match &*value { - "major" | "minor" | "patch" => { + "major" | "minor" | "patch" | "alpha" | "beta" | "rc" | "dev" | "post" + | "stable" => { return Err(anyhow!( "Invalid version `{value}`, did you mean to pass `--bump {value}`?" )); @@ -168,8 +169,135 @@ pub(crate) async fn project_version( } }, } - } else if let Some(bump) = bump { - Some(bumped_version(&old_version, bump, printer)?) + } else if !bump.is_empty() { + // While we can rationalize many of these combinations of operations together, + // we want to conservatively refuse to support any of them until users demand it. + // + // The most complex thing we *do* allow is `--bump major --bump beta --bump dev` + // because that makes perfect sense and is reasonable to do. + let release_components: Vec<_> = bump + .iter() + .filter(|bump| { + matches!( + bump, + VersionBump::Major | VersionBump::Minor | VersionBump::Patch + ) + }) + .collect(); + let prerelease_components: Vec<_> = bump + .iter() + .filter(|bump| { + matches!( + bump, + VersionBump::Alpha | VersionBump::Beta | VersionBump::Rc | VersionBump::Dev + ) + }) + .collect(); + let post_count = bump + .iter() + .filter(|bump| *bump == &VersionBump::Post) + .count(); + let stable_count = bump + .iter() + .filter(|bump| *bump == &VersionBump::Stable) + .count(); + + // Very little reason to do "bump to stable" and then do other things, + // even if we can make sense of it. + if stable_count > 0 && bump.len() > 1 { + let components = bump + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "`--bump stable` cannot be used with another `--bump` value, got: {components}" + )); + } + + // Very little reason to "bump to post" and then do other things, + // how is it a post-release otherwise? + if post_count > 0 && bump.len() > 1 { + let components = bump + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "`--bump post` cannot be used with another `--bump` value, got: {components}" + )); + } + + // `--bump major --bump minor` makes perfect sense (1.2.3 => 2.1.0) + // ...but it's weird and probably a mistake? + // `--bump major --bump major` perfect sense (1.2.3 => 3.0.0) + // ...but it's weird and probably a mistake? + if release_components.len() > 1 { + let components = release_components + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "Only one release version component can be provided to `--bump`, got: {components}" + )); + } + + // `--bump alpha --bump beta` is basically completely incoherent + // `--bump beta --bump beta` makes perfect sense (1.2.3b4 => 1.2.3b6) + // ...but it's weird and probably a mistake? + // `--bump beta --bump dev` makes perfect sense (1.2.3 => 1.2.3b1.dev1) + // ...but we want to discourage mixing `dev` with pre-releases + if prerelease_components.len() > 1 { + let components = prerelease_components + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "); + return Err(anyhow!( + "Only one pre-release version component can be provided to `--bump`, got: {components}" + )); + } + + // Sort the given commands so the user doesn't have to care about + // the ordering of `--bump minor --bump beta` (only one ordering is ever useful) + bump.sort(); + + // Apply all the bumps + let mut new_version = old_version.clone(); + for bump in &bump { + let command = match *bump { + VersionBump::Major => BumpCommand::BumpRelease { index: 0 }, + VersionBump::Minor => BumpCommand::BumpRelease { index: 1 }, + VersionBump::Patch => BumpCommand::BumpRelease { index: 2 }, + VersionBump::Alpha => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Alpha, + }, + VersionBump::Beta => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Beta, + }, + VersionBump::Rc => BumpCommand::BumpPrerelease { + kind: PrereleaseKind::Rc, + }, + VersionBump::Post => BumpCommand::BumpPost, + VersionBump::Dev => BumpCommand::BumpDev, + VersionBump::Stable => BumpCommand::MakeStable, + }; + new_version.bump(command); + } + + if new_version <= old_version { + if old_version.is_stable() && new_version.is_pre() { + return Err(anyhow!( + "{old_version} => {new_version} didn't increase the version; when bumping to a pre-release version you also need to increase a release version component, e.g., with `--bump `" + )); + } + return Err(anyhow!( + "{old_version} => {new_version} didn't increase the version; provide the exact version to force an update" + )); + } + + Some(new_version) } else { None }; @@ -569,35 +697,3 @@ fn print_version( } Ok(()) } - -fn bumped_version(from: &Version, bump: VersionBump, printer: Printer) -> Result { - // All prereleasey details "carry to 0" with every currently supported mode of `--bump` - // We could go out of our way to preserve epoch information but no one uses those... - if from.any_prerelease() || from.is_post() || from.is_local() || from.epoch() > 0 { - writeln!( - printer.stderr(), - "warning: prerelease information will be cleared as part of the version bump" - )?; - } - - let index = match bump { - VersionBump::Major => 0, - VersionBump::Minor => 1, - VersionBump::Patch => 2, - }; - - // Use `max` here to try to do 0.2 => 0.3 instead of 0.2 => 0.3.0 - let old_parts = from.release(); - let len = old_parts.len().max(index + 1); - let new_release_vec = (0..len) - .map(|i| match i.cmp(&index) { - // Everything before the bumped value is preserved (or is an implicit 0) - Ordering::Less => old_parts.get(i).copied().unwrap_or(0), - // This is the value to bump (could be implicit 0) - Ordering::Equal => old_parts.get(i).copied().unwrap_or(0) + 1, - // Everything after the bumped value becomes 0 - Ordering::Greater => 0, - }) - .collect::>(); - Ok(Version::new(new_release_vec)) -} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index e22eb801f..28a20f373 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -2041,7 +2041,7 @@ async fn run_project( let strict = project_was_explicit || globals.preview.is_enabled() || args.dry_run - || args.bump.is_some() + || !args.bump.is_empty() || args.value.is_some() || args.package.is_some(); Box::pin(commands::project_version( diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 673f76ebd..ed86608ed 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -1564,7 +1564,7 @@ impl RemoveSettings { #[derive(Debug, Clone)] pub(crate) struct VersionSettings { pub(crate) value: Option, - pub(crate) bump: Option, + pub(crate) bump: Vec, pub(crate) short: bool, pub(crate) output_format: VersionFormat, pub(crate) dry_run: bool, diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 97d30f4f4..ab09833f9 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -512,7 +512,6 @@ requires-python = ">=3.12" myproject 1.10.31.dev10 => 2.0.0 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -550,10 +549,9 @@ requires-python = ">=3.12" success: true exit_code: 0 ----- stdout ----- - myproject 1!2a3.post4.dev5+deadbeef6 => 3 + myproject 1!2a3.post4.dev5+deadbeef6 => 1!3+deadbeef6 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -564,7 +562,295 @@ requires-python = ">=3.12" @r#" [project] name = "myproject" - version = "3" + version = "1!3+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// Pass a ton of bump flags to a complex version +// The flags are in a messy order and some are duplicated, +// Under extremely permissive semantics this could be allowed, but right +// now it fails for a dozen reasons! +#[test] +fn many_bump_complex() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("patch") + .arg("--bump").arg("alpha") + .arg("--bump").arg("minor") + .arg("--bump").arg("dev") + .arg("--bump").arg("minor") + .arg("--bump").arg("post") + .arg("--bump").arg("post"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump post` cannot be used with another `--bump` value, got: major, patch, alpha, minor, dev, minor, post, post + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post6.dev7+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump stable +#[test] +fn bump_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump alpha +#[test] +fn bump_alpha() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a6+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a6+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump beta +#[test] +fn bump_beta() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("beta"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4b1+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4b1+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump rc +#[test] +fn bump_rc() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("rc"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4rc1+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4rc1+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump post +#[test] +fn bump_post() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("post"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a5.post7+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post7+deadbeef6" + requires-python = ">=3.12" + "# + ); + Ok(()) +} + +// --bump dev +#[test] +fn bump_dev() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "9!2.3.4a5.post6.dev7+deadbeef6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("dev"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 9!2.3.4a5.post6.dev7+deadbeef6 => 9!2.3.4a5.post6.dev8+deadbeef6 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + + let pyproject = fs_err::read_to_string(&pyproject_toml)?; + assert_snapshot!( + pyproject, + @r#" + [project] + name = "myproject" + version = "9!2.3.4a5.post6.dev8+deadbeef6" requires-python = ">=3.12" "# ); @@ -594,7 +880,6 @@ requires-python = ">=3.12" myproject 1.10.31.post10 => 2.0.0 ----- stderr ----- - warning: prerelease information will be cleared as part of the version bump Resolved 1 package in [TIME] Audited in [TIME] "); @@ -612,6 +897,317 @@ requires-python = ">=3.12" Ok(()) } +// --bump stable but it decreases the version +#[test] +fn bump_decrease_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4.post6" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4.post6 => 2.3.4 didn't increase the version; provide the exact version to force an update + "); + Ok(()) +} + +// --bump alpha but it decreases the version by reverting beta +#[test] +fn bump_decrease_alpha_beta() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4b5" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4b5 => 2.3.4a1 didn't increase the version; provide the exact version to force an update + "); + Ok(()) +} + +// --bump alpha but it decreases the version from a stable +#[test] +fn bump_decrease_alpha_stable() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: 2.3.4 => 2.3.4a1 didn't increase the version; when bumping to a pre-release version you also need to increase a release version component, e.g., with `--bump ` + "); + Ok(()) +} + +// --bump major twice +#[test] +fn bump_double_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("major"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one release version component can be provided to `--bump`, got: major, major + "); + Ok(()) +} + +// --bump alpha twice +#[test] +fn bump_double_alpha() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha") + .arg("--bump").arg("alpha"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one pre-release version component can be provided to `--bump`, got: alpha, alpha + "); + Ok(()) +} + +// --bump stable --bump major +#[test] +fn bump_stable_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("stable") + .arg("--bump").arg("major"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump stable` cannot be used with another `--bump` value, got: stable, major + "); + Ok(()) +} + +// --bump major --bump alpha +#[test] +fn bump_alpha_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0a1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump major --bump minor +#[test] +fn bump_minor_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("alpha"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0a1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump alpha --bump dev +#[test] +fn bump_alpha_dev() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("alpha") + .arg("--bump").arg("dev"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Only one pre-release version component can be provided to `--bump`, got: alpha, dev + "); + Ok(()) +} + +// --bump major --bump dev +#[test] +fn bump_dev_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("dev"), @r" + success: true + exit_code: 0 + ----- stdout ----- + myproject 2.3.4 => 3.0.0.dev1 + + ----- stderr ----- + Resolved 1 package in [TIME] + Audited in [TIME] + "); + Ok(()) +} + +// --bump major --bump post +#[test] +fn bump_post_major() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" +[project] +name = "myproject" +version = "2.3.4" +requires-python = ">=3.12" +"#, + )?; + + uv_snapshot!(context.filters(), context.version() + .arg("--bump").arg("major") + .arg("--bump").arg("post"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: `--bump post` cannot be used with another `--bump` value, got: major, post + "); + Ok(()) +} + // Set version --dry-run #[test] fn version_set_dry() -> Result<()> { diff --git a/docs/guides/package.md b/docs/guides/package.md index ce5bae7f9..c6017986d 100644 --- a/docs/guides/package.md +++ b/docs/guides/package.md @@ -55,6 +55,70 @@ Alternatively, `uv build ` will build the package in the specified director running `uv build --no-sources` to ensure that the package builds correctly when `tool.uv.sources` is disabled, as is the case when using other build tools, like [`pypa/build`](https://github.com/pypa/build). +## Updating your version + +The `uv version` command provides conveniences for updating the version of your package before you +publish it. +[See the project docs for reading your package's version](./projects.md#managing-version). + +To update to an exact version, provide it as a positional argument: + +```console +$ uv version 1.0.0 +hello-world 0.7.0 => 1.0.0 +``` + +To preview the change without updating the `pyproject.toml`, use the `--dry-run` flag: + +```console +$ uv version 2.0.0 --dry-run +hello-world 1.0.0 => 2.0.0 +$ uv version +hello-world 1.0.0 +``` + +To increase the version of your package semantics, use the `--bump` option: + +```console +$ uv version --bump minor +hello-world 1.2.3 => 1.3.0 +``` + +The `--bump` option supports the following common version components: `major`, `minor`, `patch`, +`stable`, `alpha`, `beta`, `rc`, `post`, and `dev`. When provided more than once, the components +will be applied in order, from largest (`major`) to smallest (`dev`). + +To move from a stable to pre-release version, bump one of the major, minor, or patch components in +addition to the pre-release component: + +```console +$ uv version --bump patch --bump beta +hello-world 1.3.0 => 1.3.1b1 +$ uv version --bump major --bump alpha +hello-world 1.3.0 => 2.0.0a1 +``` + +When moving from a pre-release to a new pre-release version, just bump the relevant pre-release +component: + +```console +uv version --bump beta +hello-world 1.3.0b1 => 1.3.1b2 +``` + +When moving from a pre-release to a stable version, the `stable` option can be used to clear the +pre-release component: + +```console +uv version --bump stable +hello-world 1.3.1b2 => 1.3.1 +``` + +!!! info + + By default, when `uv version` modifies the project it will perform a lock and sync. To + prevent locking and syncing, use `--frozen`, or, to just prevent syncing, use `--no-sync`. + ## Publishing your package Publish your package with `uv publish`: diff --git a/docs/guides/projects.md b/docs/guides/projects.md index 0fd97eb0d..fc6f34f2d 100644 --- a/docs/guides/projects.md +++ b/docs/guides/projects.md @@ -160,6 +160,38 @@ version, while keeping the rest of the lockfile intact. See the documentation on [managing dependencies](../concepts/projects/dependencies.md) for more details. +## Managing version + +The `uv version` command can be used to read your package's version. + +To get the version of your package, run `uv version`: + +```console +$ uv version +hello-world 0.7.0 +``` + +To get the version without the package name, use the `--short` option: + +```console +$ uv version --short +0.7.0 +``` + +To get version information in a JSON format, use the `--output-format json` option: + +```console +$ uv version --output-format json +{ + "package_name": "hello-world", + "version": "0.7.0", + "commit_info": null +} +``` + +See the [publishing guide](./package.md#updating-your-version) for details on updating your package +version. + ## Running commands `uv run` can be used to run arbitrary scripts or commands in your project environment. diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 17fe6cfce..bd828acda 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -787,11 +787,18 @@ uv version [OPTIONS] [VALUE]

Expects to receive either a hostname (e.g., localhost), a host-port pair (e.g., localhost:8080), or a URL (e.g., https://localhost).

WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use --allow-insecure-host in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.

May also be set with the UV_INSECURE_HOST environment variable.

--bump bump

Update the project version using the given semantics

+

This flag can be passed multiple times.

Possible values:

    -
  • major: Increase the major version (1.2.3 => 2.0.0)
  • -
  • minor: Increase the minor version (1.2.3 => 1.3.0)
  • -
  • patch: Increase the patch version (1.2.3 => 1.2.4)
  • +
  • major: Increase the major version (e.g., 1.2.3 => 2.0.0)
  • +
  • minor: Increase the minor version (e.g., 1.2.3 => 1.3.0)
  • +
  • patch: Increase the patch version (e.g., 1.2.3 => 1.2.4)
  • +
  • stable: Move from a pre-release to stable version (e.g., 1.2.3b4.post5.dev6 => 1.2.3)
  • +
  • alpha: Increase the alpha version (e.g., 1.2.3a4 => 1.2.3a5)
  • +
  • beta: Increase the beta version (e.g., 1.2.3b4 => 1.2.3b5)
  • +
  • rc: Increase the rc version (e.g., 1.2.3rc4 => 1.2.3rc5)
  • +
  • post: Increase the post version (e.g., 1.2.3.post5 => 1.2.3.post6)
  • +
  • dev: Increase the dev version (e.g., 1.2.3a4.dev6 => 1.2.3.dev7)
--cache-dir cache-dir

Path to the cache directory.

Defaults to $XDG_CACHE_HOME/uv or $HOME/.cache/uv on macOS and Linux, and %LOCALAPPDATA%\uv\cache on Windows.

To view the location of the cache directory, run uv cache dir.