diff --git a/src/cargo/util/toml_mut/upgrade.rs b/src/cargo/util/toml_mut/upgrade.rs index 13d5563450c..b81f51ec5e3 100644 --- a/src/cargo/util/toml_mut/upgrade.rs +++ b/src/cargo/util/toml_mut/upgrade.rs @@ -15,12 +15,16 @@ pub(crate) fn upgrade_requirement( // Empty matches everything, no-change. Ok(None) } else { - let comparators: CargoResult> = raw_req + let comparators: Vec<_> = raw_req .comparators .into_iter() + // Don't downgrade if pre-release was used, see https://github.com/rust-lang/cargo/issues/14178 and https://github.com/rust-lang/cargo/issues/13290. + .filter(|p| p.pre.is_empty() || matches_greater(p, version)) .map(|p| set_comparator(p, version)) - .collect(); - let comparators = comparators?; + .collect::>()?; + if comparators.is_empty() { + return Ok(None); + } let new_req = semver::VersionReq { comparators }; let mut new_req_text = new_req.to_string(); if new_req_text.starts_with('^') && !req.starts_with('^') { @@ -74,6 +78,33 @@ fn set_comparator( } } +// See https://github.com/dtolnay/semver/blob/69efd3cc770ead273a06ad1788477b3092996d29/src/eval.rs#L64-L88 +fn matches_greater(cmp: &semver::Comparator, ver: &semver::Version) -> bool { + if ver.major != cmp.major { + return ver.major > cmp.major; + } + + match cmp.minor { + None => return false, + Some(minor) => { + if ver.minor != minor { + return ver.minor > minor; + } + } + } + + match cmp.patch { + None => return false, + Some(patch) => { + if ver.patch != patch { + return ver.patch > patch; + } + } + } + + ver.pre > cmp.pre +} + fn assign_partial_req( version: &semver::Version, mut pred: semver::Comparator, @@ -217,5 +248,17 @@ mod test { assert_req_bump("1.1.1", "=1.0.0", "=1.1.1"); assert_req_bump("2.0.0", "=1.0.0", "=2.0.0"); } + + #[test] + fn greater_prerelease() { + assert_req_bump("1.7.0", "2.0.0-beta.21", None); + assert_req_bump("1.7.0", "=2.0.0-beta.21", None); + assert_req_bump("1.7.0", "~2.0.0-beta.21", None); + assert_req_bump("2.0.0-beta.20", "2.0.0-beta.21", None); + assert_req_bump("2.0.0-beta.21", "2.0.0-beta.21", None); + assert_req_bump("2.0.0-beta.22", "2.0.0-beta.21", "2.0.0-beta.22"); + assert_req_bump("2.0.0", "2.0.0-beta.21", "2.0.0"); + assert_req_bump("3.0.0", "2.0.0-beta.21", "3.0.0"); + } } } diff --git a/tests/testsuite/update.rs b/tests/testsuite/update.rs index f27fd68881a..0b66cb38afd 100644 --- a/tests/testsuite/update.rs +++ b/tests/testsuite/update.rs @@ -2619,3 +2619,94 @@ fn update_breaking_mixed_pinning_renaming() { "#]], ); } + +#[cargo_test] +fn update_breaking_pre_release_downgrade() { + Package::new("bar", "2.0.0-beta.21").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + + [dependencies] + bar = "2.0.0-beta.21" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile").run(); + + // The purpose of this test is + // to demonstrate that `update --breaking` will not try to downgrade to the latest stable version (1.7.0), + // but will rather keep the latest pre-release (2.0.0-beta.21). + Package::new("bar", "1.7.0").publish(); + p.cargo("update -Zunstable-options --breaking bar") + .masquerade_as_nightly_cargo(&["update-breaking"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index + +"#]]) + .run(); +} + +#[cargo_test] +fn update_breaking_pre_release_upgrade() { + Package::new("bar", "2.0.0-beta.21").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + authors = [] + + [dependencies] + bar = "2.0.0-beta.21" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("generate-lockfile").run(); + + // TODO: `2.0.0-beta.21` can be upgraded to `2.0.0-beta.22` + Package::new("bar", "2.0.0-beta.22").publish(); + p.cargo("update -Zunstable-options --breaking bar") + .masquerade_as_nightly_cargo(&["update-breaking"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index + +"#]]) + .run(); + // TODO: `2.0.0-beta.21` can be upgraded to `2.0.0` + Package::new("bar", "2.0.0").publish(); + p.cargo("update -Zunstable-options --breaking bar") + .masquerade_as_nightly_cargo(&["update-breaking"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index + +"#]]) + .run(); + + Package::new("bar", "3.0.0").publish(); + p.cargo("update -Zunstable-options --breaking bar") + .masquerade_as_nightly_cargo(&["update-breaking"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[UPGRADING] bar ^2.0.0-beta.21 -> ^3.0.0 +[LOCKING] 1 package to latest compatible version +[UPDATING] bar v2.0.0-beta.21 -> v3.0.0 + +"#]]) + .run(); +}