diff --git a/src/bin/cargo/commands/install.rs b/src/bin/cargo/commands/install.rs index 5fefb7adfff..0f97071e69a 100644 --- a/src/bin/cargo/commands/install.rs +++ b/src/bin/cargo/commands/install.rs @@ -286,6 +286,12 @@ fn parse_semver_flag(v: &str) -> CargoResult { .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::() { diff --git a/src/bin/cargo/commands/yank.rs b/src/bin/cargo/commands/yank.rs index 5401b32de2f..b2bc147f0c9 100644 --- a/src/bin/cargo/commands/yank.rs +++ b/src/bin/cargo/commands/yank.rs @@ -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)) } diff --git a/src/cargo/core/source_id.rs b/src/cargo/core/source_id.rs index 8c916b7b445..27bf89f1f47 100644 --- a/src/cargo/core/source_id.rs +++ b/src/cargo/core/source_id.rs @@ -523,8 +523,16 @@ impl SourceId { version: semver::Version, precise: &str, ) -> CargoResult { - 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 { diff --git a/src/cargo/ops/cargo_add/crate_spec.rs b/src/cargo/ops/cargo_add/crate_spec.rs index 47959b464b0..9fb48fdb443 100644 --- a/src/cargo/ops/cargo_add/crate_spec.rs +++ b/src/cargo/ops/cargo_add/crate_spec.rs @@ -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 { diff --git a/tests/testsuite/cargo_add/mod.rs b/tests/testsuite/cargo_add/mod.rs index 050baa8e16f..1f1d3c0a475 100644 --- a/tests/testsuite/cargo_add/mod.rs +++ b/tests/testsuite/cargo_add/mod.rs @@ -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; diff --git a/tests/testsuite/cargo_add/prefixed_v_in_version/in b/tests/testsuite/cargo_add/prefixed_v_in_version/in new file mode 120000 index 00000000000..6c6a27fcfb5 --- /dev/null +++ b/tests/testsuite/cargo_add/prefixed_v_in_version/in @@ -0,0 +1 @@ +../add-basic.in \ No newline at end of file diff --git a/tests/testsuite/cargo_add/prefixed_v_in_version/mod.rs b/tests/testsuite/cargo_add/prefixed_v_in_version/mod.rs new file mode 100644 index 00000000000..e36b65242b8 --- /dev/null +++ b/tests/testsuite/cargo_add/prefixed_v_in_version/mod.rs @@ -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); +} diff --git a/tests/testsuite/cargo_add/prefixed_v_in_version/out/Cargo.toml b/tests/testsuite/cargo_add/prefixed_v_in_version/out/Cargo.toml new file mode 100644 index 00000000000..946b7c86bf0 --- /dev/null +++ b/tests/testsuite/cargo_add/prefixed_v_in_version/out/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +[package] +name = "cargo-list-test-fixture" +version = "0.0.0" +edition = "2015" diff --git a/tests/testsuite/cargo_add/prefixed_v_in_version/stderr.term.svg b/tests/testsuite/cargo_add/prefixed_v_in_version/stderr.term.svg new file mode 100644 index 00000000000..86e034ff602 --- /dev/null +++ b/tests/testsuite/cargo_add/prefixed_v_in_version/stderr.term.svg @@ -0,0 +1,37 @@ + + + + + + + error: the version provided, `v0.0.1` is not a valid SemVer requirement + + + + help: changing the package to `foo@0.0.1` + + + + Caused by: + + unexpected character 'v' while parsing major version number + + + + + + diff --git a/tests/testsuite/install.rs b/tests/testsuite/install.rs index ac181b6fc11..dfff7a680c8 100644 --- a/tests/testsuite/install.rs +++ b/tests/testsuite/install.rs @@ -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[@]]...': 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(); +} diff --git a/tests/testsuite/update.rs b/tests/testsuite/update.rs index 87c36293dc2..613bc669142 100644 --- a/tests/testsuite/update.rs +++ b/tests/testsuite/update.rs @@ -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(); +} diff --git a/tests/testsuite/yank.rs b/tests/testsuite/yank.rs index 04967a7a347..f97c36bf186 100644 --- a/tests/testsuite/yank.rs +++ b/tests/testsuite/yank.rs @@ -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(); +}