Allow explicit values with `uv version --bump` (#16555)

Resolves https://github.com/astral-sh/uv/issues/16427

This PR updates `uv version --bump` so you can pin the exact number
you’re targeting, i.e. `--bump patch=10` or `--bump dev=42`. The
command-line interface now parses those `component=value` flags, and the
bump logic actually sets the version to the number you asked for.
This commit is contained in:
liam 2025-11-11 12:27:32 -05:00 committed by GitHub
parent 3ccad58166
commit 63ab247765
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 786 additions and 90 deletions

View File

@ -1,11 +1,14 @@
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::{self, Display, Formatter};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use clap::builder::Styles; use clap::ValueEnum;
use clap::builder::styling::{AnsiColor, Effects, Style}; use clap::builder::styling::{AnsiColor, Effects, Style};
use clap::builder::{PossibleValue, Styles, TypedValueParser, ValueParserFactory};
use clap::error::ErrorKind;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use uv_auth::Service; use uv_auth::Service;
@ -587,8 +590,8 @@ pub struct VersionArgs {
/// Update the project version using the given semantics /// Update the project version using the given semantics
/// ///
/// This flag can be passed multiple times. /// This flag can be passed multiple times.
#[arg(group = "operation", long)] #[arg(group = "operation", long, value_name = "BUMP[=VALUE]")]
pub bump: Vec<VersionBump>, pub bump: Vec<VersionBumpSpec>,
/// Don't write a new version to the `pyproject.toml` /// Don't write a new version to the `pyproject.toml`
/// ///
@ -698,8 +701,8 @@ pub enum VersionBump {
Dev, Dev,
} }
impl std::fmt::Display for VersionBump { impl Display for VersionBump {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let string = match self { let string = match self {
Self::Major => "major", Self::Major => "major",
Self::Minor => "minor", Self::Minor => "minor",
@ -715,6 +718,110 @@ impl std::fmt::Display for VersionBump {
} }
} }
impl FromStr for VersionBump {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"major" => Ok(Self::Major),
"minor" => Ok(Self::Minor),
"patch" => Ok(Self::Patch),
"stable" => Ok(Self::Stable),
"alpha" => Ok(Self::Alpha),
"beta" => Ok(Self::Beta),
"rc" => Ok(Self::Rc),
"post" => Ok(Self::Post),
"dev" => Ok(Self::Dev),
_ => Err(format!("invalid bump component `{value}`")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct VersionBumpSpec {
pub bump: VersionBump,
pub value: Option<u64>,
}
impl Display for VersionBumpSpec {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self.value {
Some(value) => write!(f, "{}={value}", self.bump),
None => self.bump.fmt(f),
}
}
}
impl FromStr for VersionBumpSpec {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let (name, value) = match input.split_once('=') {
Some((name, value)) => (name, Some(value)),
None => (input, None),
};
let bump = name.parse::<VersionBump>()?;
if bump == VersionBump::Stable && value.is_some() {
return Err("`--bump stable` does not accept a value".to_string());
}
let value = match value {
Some("") => {
return Err("`--bump` values cannot be empty".to_string());
}
Some(raw) => Some(
raw.parse::<u64>()
.map_err(|_| format!("invalid numeric value `{raw}` for `--bump {name}`"))?,
),
None => None,
};
Ok(Self { bump, value })
}
}
impl ValueParserFactory for VersionBumpSpec {
type Parser = VersionBumpSpecValueParser;
fn value_parser() -> Self::Parser {
VersionBumpSpecValueParser
}
}
#[derive(Clone, Debug)]
pub struct VersionBumpSpecValueParser;
impl TypedValueParser for VersionBumpSpecValueParser {
type Value = VersionBumpSpec;
fn parse_ref(
&self,
_cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let raw = value.to_str().ok_or_else(|| {
clap::Error::raw(
ErrorKind::InvalidUtf8,
"`--bump` values must be valid UTF-8",
)
})?;
VersionBumpSpec::from_str(raw)
.map_err(|message| clap::Error::raw(ErrorKind::InvalidValue, message))
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
VersionBump::value_variants()
.iter()
.filter_map(ValueEnum::to_possible_value),
))
}
}
#[derive(Args)] #[derive(Args)]
pub struct SelfNamespace { pub struct SelfNamespace {
#[command(subcommand)] #[command(subcommand)]

View File

@ -676,7 +676,7 @@ impl Version {
let full = self.make_full(); let full = self.make_full();
match bump { match bump {
BumpCommand::BumpRelease { index } => { BumpCommand::BumpRelease { index, value } => {
// Clear all sub-release items // Clear all sub-release items
full.pre = None; full.pre = None;
full.post = None; full.post = None;
@ -690,7 +690,9 @@ impl Version {
// Everything before the bumped value is preserved (or is an implicit 0) // Everything before the bumped value is preserved (or is an implicit 0)
Ordering::Less => old_parts.get(i).copied().unwrap_or(0), Ordering::Less => old_parts.get(i).copied().unwrap_or(0),
// This is the value to bump (could be implicit 0) // This is the value to bump (could be implicit 0)
Ordering::Equal => old_parts.get(i).copied().unwrap_or(0) + 1, Ordering::Equal => {
value.unwrap_or_else(|| old_parts.get(i).copied().unwrap_or(0) + 1)
}
// Everything after the bumped value becomes 0 // Everything after the bumped value becomes 0
Ordering::Greater => 0, Ordering::Greater => 0,
}) })
@ -703,37 +705,50 @@ impl Version {
full.post = None; full.post = None;
full.dev = None; full.dev = None;
} }
BumpCommand::BumpPrerelease { kind } => { BumpCommand::BumpPrerelease { kind, value } => {
// Clear all sub-prerelease items // Clear all sub-prerelease items
full.post = None; full.post = None;
full.dev = None; full.dev = None;
if let Some(value) = value {
// Either bump the matching kind or set to 1 full.pre = Some(Prerelease {
if let Some(prerelease) = &mut full.pre { kind,
if prerelease.kind == kind { number: value,
prerelease.number += 1; });
return; } else {
// 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 });
} }
full.pre = Some(Prerelease { kind, number: 1 });
} }
BumpCommand::BumpPost => { BumpCommand::BumpPost { value } => {
// Clear sub-post items // Clear sub-post items
full.dev = None; full.dev = None;
if let Some(value) = value {
// Either bump or set to 1 full.post = Some(value);
if let Some(post) = &mut full.post {
*post += 1;
} else { } else {
full.post = Some(1); // Either bump or set to 1
if let Some(post) = &mut full.post {
*post += 1;
} else {
full.post = Some(1);
}
} }
} }
BumpCommand::BumpDev => { BumpCommand::BumpDev { value } => {
// Either bump or set to 1 if let Some(value) = value {
if let Some(dev) = &mut full.dev { full.dev = Some(value);
*dev += 1;
} else { } else {
full.dev = Some(1); // Either bump or set to 1
if let Some(dev) = &mut full.dev {
*dev += 1;
} else {
full.dev = Some(1);
}
} }
} }
} }
@ -1018,22 +1033,32 @@ impl FromStr for Version {
/// Various ways to "bump" a version /// Various ways to "bump" a version
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum BumpCommand { pub enum BumpCommand {
/// Bump the release component /// Bump or set the release component
BumpRelease { BumpRelease {
/// The release component to bump (0 is major, 1 is minor, 2 is patch) /// The release component to bump (0 is major, 1 is minor, 2 is patch)
index: usize, index: usize,
/// Explicit value to set; when absent the component is incremented
value: Option<u64>,
}, },
/// Bump the prerelease component /// Bump or set the prerelease component
BumpPrerelease { BumpPrerelease {
/// prerelease component to bump /// prerelease component to bump
kind: PrereleaseKind, kind: PrereleaseKind,
/// Explicit value to set; when absent the component is incremented
value: Option<u64>,
}, },
/// Bump to the associated stable release /// Bump to the associated stable release
MakeStable, MakeStable,
/// Bump the post component /// Bump or set the post component
BumpPost, BumpPost {
/// Bump the dev component /// Explicit value to set; when absent the component is incremented
BumpDev, value: Option<u64>,
},
/// Bump or set the dev component
BumpDev {
/// Explicit value to set; when absent the component is incremented
value: Option<u64>,
},
} }
/// A small representation of a version. /// A small representation of a version.
@ -4239,36 +4264,57 @@ mod tests {
fn bump_major() { fn bump_major() {
// one digit // one digit
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "1"); assert_eq!(version.to_string().as_str(), "1");
// two digit // two digit
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0"); assert_eq!(version.to_string().as_str(), "2.0");
// three digit (zero major) // three digit (zero major)
let mut version = "0.1.2".parse::<Version>().unwrap(); let mut version = "0.1.2".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.0.0"); assert_eq!(version.to_string().as_str(), "1.0.0");
// three digit (non-zero major) // three digit (non-zero major)
let mut version = "1.2.3".parse::<Version>().unwrap(); let mut version = "1.2.3".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0.0"); assert_eq!(version.to_string().as_str(), "2.0.0");
// four digit // four digit
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "2.0.0.0"); assert_eq!(version.to_string().as_str(), "2.0.0.0");
// All the version junk // All the version junk
let mut version = "5!1.7.3.5b2.post345.dev456+local" let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>() .parse::<Version>()
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!2.0.0.0+local"); assert_eq!(version.to_string().as_str(), "5!2.0.0.0+local");
version.bump(BumpCommand::BumpRelease { index: 0 }); version.bump(BumpCommand::BumpRelease {
index: 0,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!3.0.0.0+local"); assert_eq!(version.to_string().as_str(), "5!3.0.0.0+local");
} }
@ -4278,31 +4324,49 @@ mod tests {
fn bump_minor() { fn bump_minor() {
// one digit // one digit
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "0.1"); assert_eq!(version.to_string().as_str(), "0.1");
// two digit // two digit
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.6"); assert_eq!(version.to_string().as_str(), "1.6");
// three digit (non-zero major) // three digit (non-zero major)
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.4.0"); assert_eq!(version.to_string().as_str(), "5.4.0");
// four digit // four digit
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.3.0.0"); assert_eq!(version.to_string().as_str(), "1.3.0.0");
// All the version junk // All the version junk
let mut version = "5!1.7.3.5b2.post345.dev456+local" let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>() .parse::<Version>()
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.8.0.0+local"); assert_eq!(version.to_string().as_str(), "5!1.8.0.0+local");
version.bump(BumpCommand::BumpRelease { index: 1 }); version.bump(BumpCommand::BumpRelease {
index: 1,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.9.0.0+local"); assert_eq!(version.to_string().as_str(), "5!1.9.0.0+local");
} }
@ -4312,31 +4376,49 @@ mod tests {
fn bump_patch() { fn bump_patch() {
// one digit // one digit
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "0.0.1"); assert_eq!(version.to_string().as_str(), "0.0.1");
// two digit // two digit
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.5.1"); assert_eq!(version.to_string().as_str(), "1.5.1");
// three digit // three digit
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5.3.7"); assert_eq!(version.to_string().as_str(), "5.3.7");
// four digit // four digit
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "1.2.4.0"); assert_eq!(version.to_string().as_str(), "1.2.4.0");
// All the version junk // All the version junk
let mut version = "5!1.7.3.5b2.post345.dev456+local" let mut version = "5!1.7.3.5b2.post345.dev456+local"
.parse::<Version>() .parse::<Version>()
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.4.0+local"); assert_eq!(version.to_string().as_str(), "5!1.7.4.0+local");
version.bump(BumpCommand::BumpRelease { index: 2 }); version.bump(BumpCommand::BumpRelease {
index: 2,
value: None,
});
assert_eq!(version.to_string().as_str(), "5!1.7.5.0+local"); assert_eq!(version.to_string().as_str(), "5!1.7.5.0+local");
} }
@ -4348,6 +4430,7 @@ mod tests {
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "0a1"); assert_eq!(version.to_string().as_str(), "0a1");
@ -4355,6 +4438,7 @@ mod tests {
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.5a1"); assert_eq!(version.to_string().as_str(), "1.5a1");
@ -4362,6 +4446,7 @@ mod tests {
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5.3.6a1"); assert_eq!(version.to_string().as_str(), "5.3.6a1");
@ -4369,6 +4454,7 @@ mod tests {
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.2.3.4a1"); assert_eq!(version.to_string().as_str(), "1.2.3.4a1");
@ -4378,10 +4464,12 @@ mod tests {
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5a1+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5a1+local");
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5a2+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5a2+local");
} }
@ -4394,6 +4482,7 @@ mod tests {
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "0b1"); assert_eq!(version.to_string().as_str(), "0b1");
@ -4401,6 +4490,7 @@ mod tests {
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.5b1"); assert_eq!(version.to_string().as_str(), "1.5b1");
@ -4408,6 +4498,7 @@ mod tests {
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5.3.6b1"); assert_eq!(version.to_string().as_str(), "5.3.6b1");
@ -4415,6 +4506,7 @@ mod tests {
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.2.3.4b1"); assert_eq!(version.to_string().as_str(), "1.2.3.4b1");
@ -4424,10 +4516,12 @@ mod tests {
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b1+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5b1+local");
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2+local");
} }
@ -4440,6 +4534,7 @@ mod tests {
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "0rc1"); assert_eq!(version.to_string().as_str(), "0rc1");
@ -4447,6 +4542,7 @@ mod tests {
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.5rc1"); assert_eq!(version.to_string().as_str(), "1.5rc1");
@ -4454,6 +4550,7 @@ mod tests {
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5.3.6rc1"); assert_eq!(version.to_string().as_str(), "5.3.6rc1");
@ -4461,6 +4558,7 @@ mod tests {
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "1.2.3.4rc1"); assert_eq!(version.to_string().as_str(), "1.2.3.4rc1");
@ -4470,10 +4568,12 @@ mod tests {
.unwrap(); .unwrap();
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc1+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc1+local");
version.bump(BumpCommand::BumpPrerelease { version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
value: None,
}); });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc2+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5rc2+local");
} }
@ -4484,29 +4584,29 @@ mod tests {
fn bump_post() { fn bump_post() {
// one digit // one digit
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "0.post1"); assert_eq!(version.to_string().as_str(), "0.post1");
// two digit // two digit
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "1.5.post1"); assert_eq!(version.to_string().as_str(), "1.5.post1");
// three digit // three digit
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5.3.6.post1"); assert_eq!(version.to_string().as_str(), "5.3.6.post1");
// four digit // four digit
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "1.2.3.4.post1"); assert_eq!(version.to_string().as_str(), "1.2.3.4.post1");
// All the version junk // All the version junk
let mut version = "5!1.7.3.5b2.dev123+local".parse::<Version>().unwrap(); let mut version = "5!1.7.3.5b2.dev123+local".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post1+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post1+local");
version.bump(BumpCommand::BumpPost); version.bump(BumpCommand::BumpPost { value: None });
assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post2+local"); assert_eq!(version.to_string().as_str(), "5!1.7.3.5b2.post2+local");
} }
@ -4516,32 +4616,32 @@ mod tests {
fn bump_dev() { fn bump_dev() {
// one digit // one digit
let mut version = "0".parse::<Version>().unwrap(); let mut version = "0".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "0.dev1"); assert_eq!(version.to_string().as_str(), "0.dev1");
// two digit // two digit
let mut version = "1.5".parse::<Version>().unwrap(); let mut version = "1.5".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "1.5.dev1"); assert_eq!(version.to_string().as_str(), "1.5.dev1");
// three digit // three digit
let mut version = "5.3.6".parse::<Version>().unwrap(); let mut version = "5.3.6".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "5.3.6.dev1"); assert_eq!(version.to_string().as_str(), "5.3.6.dev1");
// four digit // four digit
let mut version = "1.2.3.4".parse::<Version>().unwrap(); let mut version = "1.2.3.4".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!(version.to_string().as_str(), "1.2.3.4.dev1"); assert_eq!(version.to_string().as_str(), "1.2.3.4.dev1");
// All the version junk // All the version junk
let mut version = "5!1.7.3.5b2.post345+local".parse::<Version>().unwrap(); let mut version = "5!1.7.3.5b2.post345+local".parse::<Version>().unwrap();
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!( assert_eq!(
version.to_string().as_str(), version.to_string().as_str(),
"5!1.7.3.5b2.post345.dev1+local" "5!1.7.3.5b2.post345.dev1+local"
); );
version.bump(BumpCommand::BumpDev); version.bump(BumpCommand::BumpDev { value: None });
assert_eq!( assert_eq!(
version.to_string().as_str(), version.to_string().as_str(),
"5!1.7.3.5b2.post345.dev2+local" "5!1.7.3.5b2.post345.dev2+local"

View File

@ -8,7 +8,7 @@ use owo_colors::OwoColorize;
use tracing::debug; use tracing::debug;
use uv_cache::Cache; use uv_cache::Cache;
use uv_cli::version::VersionInfo; use uv_cli::version::VersionInfo;
use uv_cli::{VersionBump, VersionFormat}; use uv_cli::{VersionBump, VersionBumpSpec, VersionFormat};
use uv_client::BaseClientBuilder; use uv_client::BaseClientBuilder;
use uv_configuration::{ use uv_configuration::{
Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification,
@ -56,7 +56,7 @@ pub(crate) fn self_version(
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn project_version( pub(crate) async fn project_version(
value: Option<String>, value: Option<String>,
mut bump: Vec<VersionBump>, mut bump: Vec<VersionBumpSpec>,
short: bool, short: bool,
output_format: VersionFormat, output_format: VersionFormat,
project_dir: &Path, project_dir: &Path,
@ -164,29 +164,29 @@ pub(crate) async fn project_version(
// because that makes perfect sense and is reasonable to do. // because that makes perfect sense and is reasonable to do.
let release_components: Vec<_> = bump let release_components: Vec<_> = bump
.iter() .iter()
.filter(|bump| { .filter(|spec| {
matches!( matches!(
bump, spec.bump,
VersionBump::Major | VersionBump::Minor | VersionBump::Patch VersionBump::Major | VersionBump::Minor | VersionBump::Patch
) )
}) })
.collect(); .collect();
let prerelease_components: Vec<_> = bump let prerelease_components: Vec<_> = bump
.iter() .iter()
.filter(|bump| { .filter(|spec| {
matches!( matches!(
bump, spec.bump,
VersionBump::Alpha | VersionBump::Beta | VersionBump::Rc | VersionBump::Dev VersionBump::Alpha | VersionBump::Beta | VersionBump::Rc | VersionBump::Dev
) )
}) })
.collect(); .collect();
let post_count = bump let post_count = bump
.iter() .iter()
.filter(|bump| *bump == &VersionBump::Post) .filter(|spec| spec.bump == VersionBump::Post)
.count(); .count();
let stable_count = bump let stable_count = bump
.iter() .iter()
.filter(|bump| *bump == &VersionBump::Stable) .filter(|spec| spec.bump == VersionBump::Stable)
.count(); .count();
// Very little reason to do "bump to stable" and then do other things, // Very little reason to do "bump to stable" and then do other things,
@ -252,25 +252,37 @@ pub(crate) async fn project_version(
// Apply all the bumps // Apply all the bumps
let mut new_version = old_version.clone(); let mut new_version = old_version.clone();
for bump in &bump {
let command = match *bump { for spec in &bump {
VersionBump::Major => BumpCommand::BumpRelease { index: 0 }, match spec.bump {
VersionBump::Minor => BumpCommand::BumpRelease { index: 1 }, VersionBump::Major => new_version.bump(BumpCommand::BumpRelease {
VersionBump::Patch => BumpCommand::BumpRelease { index: 2 }, index: 0,
VersionBump::Alpha => BumpCommand::BumpPrerelease { value: spec.value,
}),
VersionBump::Minor => new_version.bump(BumpCommand::BumpRelease {
index: 1,
value: spec.value,
}),
VersionBump::Patch => new_version.bump(BumpCommand::BumpRelease {
index: 2,
value: spec.value,
}),
VersionBump::Stable => new_version.bump(BumpCommand::MakeStable),
VersionBump::Alpha => new_version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Alpha, kind: PrereleaseKind::Alpha,
}, value: spec.value,
VersionBump::Beta => BumpCommand::BumpPrerelease { }),
VersionBump::Beta => new_version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Beta, kind: PrereleaseKind::Beta,
}, value: spec.value,
VersionBump::Rc => BumpCommand::BumpPrerelease { }),
VersionBump::Rc => new_version.bump(BumpCommand::BumpPrerelease {
kind: PrereleaseKind::Rc, kind: PrereleaseKind::Rc,
}, value: spec.value,
VersionBump::Post => BumpCommand::BumpPost, }),
VersionBump::Dev => BumpCommand::BumpDev, VersionBump::Post => new_version.bump(BumpCommand::BumpPost { value: spec.value }),
VersionBump::Stable => BumpCommand::MakeStable, VersionBump::Dev => new_version.bump(BumpCommand::BumpDev { value: spec.value }),
}; }
new_version.bump(command);
} }
if new_version <= old_version { if new_version <= old_version {

View File

@ -15,7 +15,7 @@ use uv_cli::{
PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonListFormat, PythonPinArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonListFormat, PythonPinArgs,
PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, SyncArgs, SyncFormat, ToolDirArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, SyncArgs, SyncFormat, ToolDirArgs,
ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs,
VersionBump, VersionFormat, VersionBumpSpec, VersionFormat,
}; };
use uv_cli::{ use uv_cli::{
AuthorFrom, BuildArgs, ExportArgs, FormatArgs, PublishArgs, PythonDirArgs, AuthorFrom, BuildArgs, ExportArgs, FormatArgs, PublishArgs, PythonDirArgs,
@ -1792,7 +1792,7 @@ impl RemoveSettings {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct VersionSettings { pub(crate) struct VersionSettings {
pub(crate) value: Option<String>, pub(crate) value: Option<String>,
pub(crate) bump: Vec<VersionBump>, pub(crate) bump: Vec<VersionBumpSpec>,
pub(crate) short: bool, pub(crate) short: bool,
pub(crate) output_format: VersionFormat, pub(crate) output_format: VersionFormat,
pub(crate) dry_run: bool, pub(crate) dry_run: bool,

View File

@ -248,6 +248,123 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn version_bump_patch_value() -> 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 = "1.10.31"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("patch=40"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 1.10.31 => 1.10.40
----- 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 = "1.10.40"
requires-python = ">=3.12"
"#
);
Ok(())
}
#[test]
fn version_bump_minor_value() -> 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 = "1.2.3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("minor=10"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 1.2.3 => 1.10.0
----- 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 = "1.10.0"
requires-python = ">=3.12"
"#
);
Ok(())
}
#[test]
fn version_bump_major_value() -> 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=7"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 2.3.4 => 7.0.0
----- 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 = "7.0.0"
requires-python = ">=3.12"
"#
);
Ok(())
}
// Bump patch version (--short) // Bump patch version (--short)
#[test] #[test]
fn version_bump_patch_short() -> Result<()> { fn version_bump_patch_short() -> Result<()> {
@ -289,6 +406,43 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn version_bump_patch_value_must_increase() -> 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 = "0.0.12"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("patch=11"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: 0.0.12 => 0.0.11 didn't increase the version; provide the exact version to force an update
");
let pyproject = fs_err::read_to_string(&pyproject_toml)?;
assert_snapshot!(
pyproject,
@r#"
[project]
name = "myproject"
version = "0.0.12"
requires-python = ">=3.12"
"#
);
Ok(())
}
/// Preserve comments immediately preceding the version when bumping /// Preserve comments immediately preceding the version when bumping
#[test] #[test]
fn version_bump_preserves_preceding_comments() -> Result<()> { fn version_bump_preserves_preceding_comments() -> Result<()> {
@ -781,6 +935,85 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn bump_beta_with_value_existing() -> 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 = "1.2.3b4"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("beta=42"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 1.2.3b4 => 1.2.3b42
----- 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 = "1.2.3b42"
requires-python = ">=3.12"
"#
);
Ok(())
}
#[test]
fn bump_beta_with_value_new() -> 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 = "1.2.3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("beta=5")
.arg("--bump").arg("patch"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 1.2.3 => 1.2.4b5
----- 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 = "1.2.4b5"
requires-python = ">=3.12"
"#
);
Ok(())
}
// --bump rc // --bump rc
#[test] #[test]
fn bump_rc() -> Result<()> { fn bump_rc() -> Result<()> {
@ -861,6 +1094,45 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn bump_post_with_value_clears_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 = "1.2.3.post4.dev9"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("post=10"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 1.2.3.post4.dev9 => 1.2.3.post10
----- 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 = "1.2.3.post10"
requires-python = ">=3.12"
"#
);
Ok(())
}
// --bump dev // --bump dev
#[test] #[test]
fn bump_dev() -> Result<()> { fn bump_dev() -> Result<()> {
@ -901,6 +1173,125 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn bump_dev_with_value() -> 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 = "0.1.0.dev4"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("dev=42"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 0.1.0.dev4 => 0.1.0.dev42
----- 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 = "0.1.0.dev42"
requires-python = ">=3.12"
"#
);
Ok(())
}
#[test]
fn bump_patch_and_dev_value() -> 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 = "0.0.1"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("patch")
.arg("--bump").arg("dev=66463664"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 0.0.1 => 0.0.2.dev66463664
----- 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 = "0.0.2.dev66463664"
requires-python = ">=3.12"
"#
);
Ok(())
}
#[test]
fn bump_patch_and_dev_explicit_values_sorted() -> 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 = "0.1.2.dev3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("dev=0")
.arg("--bump").arg("patch=10"), @r"
success: true
exit_code: 0
----- stdout -----
myproject 0.1.2.dev3 => 0.1.10.dev0
----- 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 = "0.1.10.dev0"
requires-python = ">=3.12"
"#
);
Ok(())
}
// Bump major but the input version is .post // Bump major but the input version is .post
#[test] #[test]
fn version_major_post() -> Result<()> { fn version_major_post() -> Result<()> {
@ -941,6 +1332,84 @@ requires-python = ">=3.12"
Ok(()) Ok(())
} }
#[test]
fn bump_stable_with_value_fails() -> 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 = "1.2.3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("stable=1"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: `--bump stable` does not accept a value
");
Ok(())
}
#[test]
fn bump_empty_value_fails() -> 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 = "1.2.3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("patch="), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: `--bump` values cannot be empty
");
Ok(())
}
#[test]
fn bump_invalid_numeric_value_fails() -> 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 = "1.2.3"
requires-python = ">=3.12"
"#,
)?;
uv_snapshot!(context.filters(), context.version()
.arg("--bump").arg("dev=foo"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid numeric value `foo` for `--bump dev`
");
Ok(())
}
// --bump stable but it decreases the version // --bump stable but it decreases the version
#[test] #[test]
fn bump_decrease_stable() -> Result<()> { fn bump_decrease_stable() -> Result<()> {

View File

@ -88,6 +88,14 @@ The `--bump` option supports the following common version components: `major`, `
`stable`, `alpha`, `beta`, `rc`, `post`, and `dev`. When provided more than once, the components `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`). will be applied in order, from largest (`major`) to smallest (`dev`).
You can optionally provide a numeric value with `--bump <component>=<value>` to set the resulting
component explicitly:
```console
$ uv version --bump patch --bump dev=66463664
hello-world 0.0.1 => 0.0.2.dev66463664
```
To move from a stable to pre-release version, bump one of the major, minor, or patch components in 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: addition to the pre-release component:

View File

@ -1168,7 +1168,7 @@ uv version [OPTIONS] [VALUE]
<p>Can be provided multiple times.</p> <p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p> <p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p> <p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-version--bump"><a href="#uv-version--bump"><code>--bump</code></a> <i>bump</i></dt><dd><p>Update the project version using the given semantics</p> <p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-version--bump"><a href="#uv-version--bump"><code>--bump</code></a> <i>bump[=value]</i></dt><dd><p>Update the project version using the given semantics</p>
<p>This flag can be passed multiple times.</p> <p>This flag can be passed multiple times.</p>
<p>Possible values:</p> <p>Possible values:</p>
<ul> <ul>