Improved error message for versions prefixed with v (#15484)

### What does this PR try to resolve?

- Added an error message when version in `CRATE[@<VER>]` or `--version
<VER>` starts with 'v' for `install`, `add`, `yank` and `update
--precise <VER>`
- Check if version is valid in `cargo yank`

Fixes #12331

### How should we test and review this PR?

Added tests for each subcommand
This commit is contained in:
Weihang Lo 2025-05-04 13:44:15 +00:00 committed by GitHub
commit 6cba807e2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 234 additions and 4 deletions

View File

@ -286,6 +286,12 @@ fn parse_semver_flag(v: &str) -> CargoResult<VersionReq> {
.next()
.ok_or_else(|| format_err!("no version provided for the `--version` flag"))?;
if let Some(stripped) = v.strip_prefix("v") {
bail!(
"the version provided, `{v}` is not a valid SemVer requirement\n\n\
help: try changing the version to `{stripped}`",
)
}
let is_req = "<>=^~".contains(first) || v.contains('*');
if is_req {
match v.parse::<VersionReq>() {

View File

@ -1,5 +1,6 @@
use crate::command_prelude::*;
use anyhow::Context;
use cargo::ops;
use cargo_credential::Secret;
@ -60,5 +61,19 @@ fn resolve_crate<'k>(
krate = Some(k);
version = Some(v);
}
if let Some(version) = version {
semver::Version::parse(version).with_context(|| {
if let Some(stripped) = version.strip_prefix("v") {
return format!(
"the version provided, `{version}` is not a \
valid SemVer version\n\n\
help: try changing the version to `{stripped}`",
);
}
format!("invalid version `{version}`")
})?;
}
Ok((krate, version))
}

View File

@ -523,8 +523,16 @@ impl SourceId {
version: semver::Version,
precise: &str,
) -> CargoResult<SourceId> {
let precise = semver::Version::parse(precise)
.with_context(|| format!("invalid version format for precise version `{precise}`"))?;
let precise = semver::Version::parse(precise).with_context(|| {
if let Some(stripped) = precise.strip_prefix("v") {
return format!(
"the version provided, `{precise}` is not a \
valid SemVer version\n\n\
help: try changing the version to `{stripped}`",
);
}
format!("invalid version format for precise version `{precise}`")
})?;
Ok(SourceId::wrap(SourceIdInner {
precise: Some(Precise::Updated {

View File

@ -47,8 +47,16 @@ impl CrateSpec {
package_name?;
if let Some(version) = version {
semver::VersionReq::parse(version)
.with_context(|| format!("invalid version requirement `{version}`"))?;
semver::VersionReq::parse(version).with_context(|| {
if let Some(stripped) = version.strip_prefix("v") {
return format!(
"the version provided, `{version}` is not a \
valid SemVer requirement\n\n\
help: changing the package to `{name}@{stripped}`",
);
}
format!("invalid version requirement `{version}`")
})?;
}
let id = Self {

View File

@ -126,6 +126,7 @@ mod path_base_unstable;
mod path_dev;
mod path_inferred_name;
mod path_inferred_name_conflicts_full_feature;
mod prefixed_v_in_version;
mod preserve_dep_std_table;
mod preserve_features_sorted;
mod preserve_features_table;

View File

@ -0,0 +1 @@
../add-basic.in

View File

@ -0,0 +1,26 @@
use cargo_test_support::compare::assert_ui;
use cargo_test_support::current_dir;
use cargo_test_support::file;
use cargo_test_support::prelude::*;
use cargo_test_support::str;
use cargo_test_support::Project;
#[cargo_test]
fn case() {
cargo_test_support::registry::init();
let project = Project::from_template(current_dir!().join("in"));
let project_root = project.root();
let cwd = &project_root;
snapbox::cmd::Command::cargo_ui()
.arg("add")
.arg_line("foo@v0.0.1")
.current_dir(cwd)
.assert()
.code(101)
.stdout_eq(str![""])
.stderr_eq(file!["stderr.term.svg"]);
assert_ui().subset_matches(current_dir!().join("out"), &project_root);
}

View File

@ -0,0 +1,6 @@
[workspace]
[package]
name = "cargo-list-test-fixture"
version = "0.0.0"
edition = "2015"

View File

@ -0,0 +1,37 @@
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-red { fill: #AA0000 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-red bold">error</tspan><tspan class="bold">:</tspan><tspan> the version provided, `v0.0.1` is not a valid SemVer requirement</tspan>
</tspan>
<tspan x="10px" y="46px">
</tspan>
<tspan x="10px" y="64px"><tspan>help: changing the package to `foo@0.0.1`</tspan>
</tspan>
<tspan x="10px" y="82px">
</tspan>
<tspan x="10px" y="100px"><tspan>Caused by:</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> unexpected character 'v' while parsing major version number</tspan>
</tspan>
<tspan x="10px" y="136px">
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -2899,3 +2899,19 @@ fn dry_run_remove_orphan() {
// Ensure server is still installed after the dry run
assert_has_installed_exe(paths::cargo_home(), "server");
}
#[cargo_test]
fn prefixed_v_in_version() {
pkg("foo", "0.0.1");
cargo_process("install foo@v0.0.1")
.with_status(1)
.with_stderr_data(str![[r#"
[ERROR] invalid value 'foo@v0.0.1' for '[CRATE[@<VER>]]...': the version provided, `v0.0.1` is not a valid SemVer requirement
[HELP] try changing the version to `0.0.1`
For more information, try '--help'.
"#]])
.run();
}

View File

@ -2709,3 +2709,41 @@ fn update_breaking_pre_release_upgrade() {
"#]])
.run();
}
#[cargo_test]
fn prefixed_v_in_version() {
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
bar = "1.0.0"
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
Package::new("bar", "1.0.1").publish();
p.cargo("update bar --precise v1.0.1")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the version provided, `v1.0.1` is not a valid SemVer version
[HELP] try changing the version to `1.0.1`
Caused by:
unexpected character 'v' while parsing major version number
"#]])
.run();
}

View File

@ -212,3 +212,71 @@ fn inline_and_explicit_version() {
"#]])
.run();
}
#[cargo_test]
fn bad_version() {
let registry = registry::init();
setup("foo", "0.0.1");
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
license = "MIT"
description = "foo"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("yank foo@bar")
.replace_crates_io(registry.index_url())
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] invalid version `bar`
Caused by:
unexpected character 'b' while parsing major version number
"#]])
.run();
}
#[cargo_test]
fn prefixed_v_in_version() {
let registry = registry::init();
setup("foo", "0.0.1");
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
license = "MIT"
description = "foo"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("yank bar@v0.0.1")
.replace_crates_io(registry.index_url())
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] the version provided, `v0.0.1` is not a valid SemVer version
[HELP] try changing the version to `0.0.1`
Caused by:
unexpected character 'v' while parsing major version number
"#]])
.run();
}