Skip to content

Commit 6e80de3

Browse files
committed
Allow precise update to prerelease.
This is a very early implementation and many boundary conditions are not taken into account.
1 parent 6972630 commit 6e80de3

File tree

4 files changed

+148
-3
lines changed

4 files changed

+148
-3
lines changed

src/cargo/core/dependency.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ impl Dependency {
402402
self.matches_id(sum.package_id())
403403
}
404404

405+
pub fn matches_prerelease(&self, sum: &Summary) -> bool {
406+
let id = sum.package_id();
407+
self.inner.name == id.name()
408+
&& (self.inner.only_match_name
409+
|| (self.inner.req.matches_prerelease(id.version()) && self.inner.source_id == id.source_id()))
410+
}
411+
405412
/// Returns `true` if the package (`id`) can fulfill this dependency request.
406413
pub fn matches_ignoring_source(&self, id: PackageId) -> bool {
407414
self.package_name() == id.name() && self.version_req().matches(id.version())

src/cargo/sources/registry/mod.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,9 @@ use crate::sources::source::QueryKind;
208208
use crate::sources::source::Source;
209209
use crate::sources::PathSource;
210210
use crate::util::cache_lock::CacheLockMode;
211-
use crate::util::hex;
212211
use crate::util::interning::InternedString;
213212
use crate::util::network::PollExt;
213+
use crate::util::{hex, VersionExt};
214214
use crate::util::{restricted_names, CargoResult, Filesystem, GlobalContext, LimitErrorReader};
215215

216216
/// The `.cargo-ok` file is used to track if the source is already unpacked.
@@ -267,6 +267,7 @@ pub struct RegistrySource<'gctx> {
267267
/// warning twice, with the assumption of (`dep.package_name()` + `--precise`
268268
/// version) being sufficient to uniquely identify the same query result.
269269
selected_precise_yanked: HashSet<(InternedString, semver::Version)>,
270+
selected_precise_prerelease: HashSet<(InternedString, semver::Version)>,
270271
}
271272

272273
/// The [`config.json`] file stored in the index.
@@ -537,6 +538,7 @@ impl<'gctx> RegistrySource<'gctx> {
537538
yanked_whitelist: yanked_whitelist.clone(),
538539
ops,
539540
selected_precise_yanked: HashSet::new(),
541+
selected_precise_prerelease: HashSet::new(),
540542
}
541543
}
542544

@@ -752,7 +754,13 @@ impl<'gctx> Source for RegistrySource<'gctx> {
752754
if let Some((_, requested)) = self
753755
.source_id
754756
.precise_registry_version(dep.package_name().as_str())
755-
.filter(|(c, _)| req.matches(c))
757+
.filter(|(c, _)| {
758+
if self.gctx.cli_unstable().precise_pre_release {
759+
req.matches_prerelease(c)
760+
} else {
761+
req.matches(c)
762+
}
763+
})
756764
{
757765
req.precise_to(&requested);
758766
}
@@ -786,11 +794,19 @@ impl<'gctx> Source for RegistrySource<'gctx> {
786794
}
787795
} else {
788796
let mut precise_yanked_in_use = false;
797+
let mut precise_prerelease_in_use = false;
798+
789799
ready!(self
790800
.index
791801
.query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| {
792802
let matched = match kind {
793-
QueryKind::Exact => dep.matches(s.as_summary()),
803+
QueryKind::Exact => {
804+
if self.gctx.cli_unstable().precise_pre_release {
805+
dep.matches_prerelease(s.as_summary())
806+
} else {
807+
dep.matches(s.as_summary())
808+
}
809+
}
794810
QueryKind::Alternatives => true,
795811
QueryKind::Normalized => true,
796812
};
@@ -801,6 +817,10 @@ impl<'gctx> Source for RegistrySource<'gctx> {
801817
// leak through if they're in a whitelist (aka if they were
802818
// previously in `Cargo.lock`
803819
if !s.is_yanked() {
820+
if req.is_precise() && s.as_summary().package_id().version().is_prerelease()
821+
{
822+
precise_prerelease_in_use = true
823+
}
804824
callback(s);
805825
} else if self.yanked_whitelist.contains(&s.package_id()) {
806826
callback(s);
@@ -827,6 +847,22 @@ impl<'gctx> Source for RegistrySource<'gctx> {
827847
shell.note("if possible, try a compatible non-yanked version")?;
828848
}
829849
}
850+
if precise_prerelease_in_use && self.gctx.cli_unstable().precise_pre_release {
851+
let name = dep.package_name();
852+
let version = req
853+
.precise_version()
854+
.expect("--precise <prerelease-version> in use");
855+
if self
856+
.selected_precise_prerelease
857+
.insert((name, version.clone()))
858+
{
859+
let mut shell = self.gctx.shell();
860+
shell.warn(format_args!(
861+
"selected prerelease package `{name}@{version}`"
862+
))?;
863+
shell.note("if possible, try a compatible non-prerelease version")?;
864+
}
865+
}
830866
if called {
831867
return Poll::Ready(Ok(()));
832868
}

src/cargo/util/semver_ext.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ impl OptVersionReq {
111111
}
112112
}
113113

114+
/// Since Semver does not support prerelease versions,
115+
/// the simplest implementation is taken here without comparing the prerelease section.
116+
/// The logic here is temporary, we'll have to consider more boundary conditions later,
117+
/// and we're not sure if this part of the functionality should be implemented in semver or cargo.
118+
pub fn matches_prerelease(&self, version: &Version) -> bool {
119+
if version.is_prerelease() {
120+
let mut version = version.clone();
121+
version.pre = semver::Prerelease::EMPTY;
122+
return self.matches(&version);
123+
}
124+
self.matches(version)
125+
}
126+
114127
pub fn matches(&self, version: &Version) -> bool {
115128
match self {
116129
OptVersionReq::Any => true,

tests/testsuite/precise_pre_release.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,92 @@ fn feature_exists() {
5858
.with_stderr("")
5959
.run()
6060
}
61+
62+
#[cargo_test]
63+
fn update_pre_release() {
64+
cargo_test_support::registry::init();
65+
66+
for version in ["0.1.1", "0.1.2-pre.0"] {
67+
cargo_test_support::registry::Package::new("my-dependency", version).publish();
68+
}
69+
70+
let p = project()
71+
.file(
72+
"Cargo.toml",
73+
r#"
74+
[package]
75+
name = "package"
76+
[dependencies]
77+
my-dependency = "0.1.1"
78+
"#,
79+
)
80+
.file("src/lib.rs", "")
81+
.build();
82+
83+
p.cargo("update -p my-dependency --precise 0.1.2-pre.0 -Zprecise-pre-release")
84+
.masquerade_as_nightly_cargo(&["precise-pre-release"])
85+
// This error is suffering from #12579 but still demonstrates that updating to
86+
// a pre-release does not work on stable
87+
.with_stderr(
88+
r#"[UPDATING] `dummy-registry` index
89+
[WARNING] selected prerelease package `[email protected]`
90+
[NOTE] if possible, try a compatible non-prerelease version
91+
[UPDATING] my-dependency v0.1.1 -> v0.1.2-pre.0
92+
"#,
93+
)
94+
.run();
95+
// Use yanked version.
96+
let lockfile = p.read_lockfile();
97+
assert!(lockfile.contains("\nname = \"my-dependency\"\nversion = \"0.1.2-pre.0\""));
98+
}
99+
100+
#[cargo_test]
101+
fn update_pre_release_differ() {
102+
cargo_test_support::registry::init();
103+
104+
for version in ["0.1.2", "0.1.2-pre.0", "0.1.2-pre.1"] {
105+
cargo_test_support::registry::Package::new("my-dependency", version).publish();
106+
}
107+
108+
let p = project()
109+
.file(
110+
"Cargo.toml",
111+
r#"
112+
[package]
113+
name = "package"
114+
[dependencies]
115+
my-dependency = "0.1.2"
116+
"#,
117+
)
118+
.file("src/lib.rs", "")
119+
.build();
120+
121+
p.cargo("update -p my-dependency --precise 0.1.2-pre.0 -Zprecise-pre-release")
122+
.masquerade_as_nightly_cargo(&["precise-pre-release"])
123+
// This error is suffering from #12579 but still demonstrates that updating to
124+
// a pre-release does not work on stable
125+
.with_stderr(
126+
r#"[UPDATING] `dummy-registry` index
127+
[WARNING] selected prerelease package `[email protected]`
128+
[NOTE] if possible, try a compatible non-prerelease version
129+
[DOWNGRADING] my-dependency v0.1.2 -> v0.1.2-pre.0
130+
"#,
131+
)
132+
.run();
133+
134+
p.cargo("update -p my-dependency --precise 0.1.2-pre.1 -Zprecise-pre-release")
135+
.masquerade_as_nightly_cargo(&["precise-pre-release"])
136+
// This error is suffering from #12579 but still demonstrates that updating to
137+
// a pre-release does not work on stable
138+
.with_stderr(
139+
r#"[UPDATING] `dummy-registry` index
140+
[WARNING] selected prerelease package `[email protected]`
141+
[NOTE] if possible, try a compatible non-prerelease version
142+
[UPDATING] my-dependency v0.1.2-pre.0 -> v0.1.2-pre.1
143+
"#,
144+
)
145+
.run();
146+
147+
let lockfile = p.read_lockfile();
148+
assert!(lockfile.contains("\nname = \"my-dependency\"\nversion = \"0.1.2-pre.1\""));
149+
}

0 commit comments

Comments
 (0)