Skip to content

Commit 3e1a2dd

Browse files
committed
Auto merge of #13333 - weihanglo:precise-yank, r=Eh2406
feat(cargo-update): `--precise` to allow yanked versions
2 parents 49433a8 + caeaaa5 commit 3e1a2dd

File tree

8 files changed

+194
-22
lines changed

8 files changed

+194
-22
lines changed

src/cargo/sources/registry/mod.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ pub struct RegistrySource<'cfg> {
261261
/// Otherwise, the resolver would think that those entries no longer
262262
/// exist, and it would trigger updates to unrelated packages.
263263
yanked_whitelist: HashSet<PackageId>,
264+
/// Yanked versions that have already been selected during queries.
265+
///
266+
/// As of this writing, this is for not emitting the `--precise <yanked>`
267+
/// warning twice, with the assumption of (`dep.package_name()` + `--precise`
268+
/// version) being sufficient to uniquely identify the same query result.
269+
selected_precise_yanked: HashSet<(InternedString, semver::Version)>,
264270
}
265271

266272
/// The [`config.json`] file stored in the index.
@@ -531,6 +537,7 @@ impl<'cfg> RegistrySource<'cfg> {
531537
index: index::RegistryIndex::new(source_id, ops.index_path(), config),
532538
yanked_whitelist: yanked_whitelist.clone(),
533539
ops,
540+
selected_precise_yanked: HashSet::new(),
534541
}
535542
}
536543

@@ -748,23 +755,27 @@ impl<'cfg> Source for RegistrySource<'cfg> {
748755
.precise_registry_version(dep.package_name().as_str())
749756
.filter(|(c, _)| req.matches(c))
750757
{
751-
req.update_precise(&requested);
758+
req.precise_to(&requested);
752759
}
753760

761+
let mut called = false;
762+
let callback = &mut |s| {
763+
called = true;
764+
f(s);
765+
};
766+
754767
// If this is a locked dependency, then it came from a lock file and in
755768
// theory the registry is known to contain this version. If, however, we
756769
// come back with no summaries, then our registry may need to be
757770
// updated, so we fall back to performing a lazy update.
758771
if kind == QueryKind::Exact && req.is_locked() && !self.ops.is_updated() {
759772
debug!("attempting query without update");
760-
let mut called = false;
761773
ready!(self
762774
.index
763775
.query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| {
764776
if dep.matches(s.as_summary()) {
765777
// We are looking for a package from a lock file so we do not care about yank
766-
called = true;
767-
f(s);
778+
callback(s)
768779
}
769780
},))?;
770781
if called {
@@ -775,24 +786,47 @@ impl<'cfg> Source for RegistrySource<'cfg> {
775786
Poll::Pending
776787
}
777788
} else {
778-
let mut called = false;
789+
let mut precise_yanked_in_use = false;
779790
ready!(self
780791
.index
781792
.query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| {
782793
let matched = match kind {
783794
QueryKind::Exact => dep.matches(s.as_summary()),
784795
QueryKind::Fuzzy => true,
785796
};
797+
if !matched {
798+
return;
799+
}
786800
// Next filter out all yanked packages. Some yanked packages may
787801
// leak through if they're in a whitelist (aka if they were
788802
// previously in `Cargo.lock`
789-
if matched
790-
&& (!s.is_yanked() || self.yanked_whitelist.contains(&s.package_id()))
791-
{
792-
f(s);
793-
called = true;
803+
if !s.is_yanked() {
804+
callback(s);
805+
} else if self.yanked_whitelist.contains(&s.package_id()) {
806+
callback(s);
807+
} else if req.is_precise() {
808+
precise_yanked_in_use = true;
809+
if self.config.cli_unstable().unstable_options {
810+
callback(s);
811+
}
794812
}
795813
}))?;
814+
if precise_yanked_in_use {
815+
self.config
816+
.cli_unstable()
817+
.fail_if_stable_opt("--precise <yanked-version>", 4225)?;
818+
let name = dep.package_name();
819+
let version = req
820+
.precise_version()
821+
.expect("--precise <yanked-version> in use");
822+
if self.selected_precise_yanked.insert((name, version.clone())) {
823+
let mut shell = self.config.shell();
824+
shell.warn(format_args!(
825+
"selected package `{name}@{version}` was yanked by the author"
826+
))?;
827+
shell.note("if possible, try a compatible non-yanked version")?;
828+
}
829+
}
796830
if called {
797831
return Poll::Ready(Ok(()));
798832
}

src/cargo/util/semver_ext.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ pub enum OptVersionReq {
3333
/// The exact locked version and the original version requirement.
3434
Locked(Version, VersionReq),
3535
/// The exact requested version and the original version requirement.
36-
UpdatePrecise(Version, VersionReq),
36+
///
37+
/// This looks identical to [`OptVersionReq::Locked`] but has a different
38+
/// meaning, and is used for the `--precise` field of `cargo update`.
39+
/// See comments in [`OptVersionReq::matches`] for more.
40+
Precise(Version, VersionReq),
3741
}
3842

3943
impl OptVersionReq {
@@ -51,7 +55,7 @@ impl OptVersionReq {
5155
pub fn is_exact(&self) -> bool {
5256
match self {
5357
OptVersionReq::Any => false,
54-
OptVersionReq::Req(req) | OptVersionReq::UpdatePrecise(_, req) => {
58+
OptVersionReq::Req(req) | OptVersionReq::Precise(_, req) => {
5559
req.comparators.len() == 1 && {
5660
let cmp = &req.comparators[0];
5761
cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
@@ -67,21 +71,34 @@ impl OptVersionReq {
6771
let version = version.clone();
6872
*self = match self {
6973
Any => Locked(version, VersionReq::STAR),
70-
Req(req) | Locked(_, req) | UpdatePrecise(_, req) => Locked(version, req.clone()),
74+
Req(req) | Locked(_, req) | Precise(_, req) => Locked(version, req.clone()),
7175
};
7276
}
7377

74-
pub fn update_precise(&mut self, version: &Version) {
78+
/// Makes the requirement precise to the requested version.
79+
///
80+
/// This is used for the `--precise` field of `cargo update`.
81+
pub fn precise_to(&mut self, version: &Version) {
7582
use OptVersionReq::*;
7683
let version = version.clone();
7784
*self = match self {
78-
Any => UpdatePrecise(version, VersionReq::STAR),
79-
Req(req) | Locked(_, req) | UpdatePrecise(_, req) => {
80-
UpdatePrecise(version, req.clone())
81-
}
85+
Any => Precise(version, VersionReq::STAR),
86+
Req(req) | Locked(_, req) | Precise(_, req) => Precise(version, req.clone()),
8287
};
8388
}
8489

90+
pub fn is_precise(&self) -> bool {
91+
matches!(self, OptVersionReq::Precise(..))
92+
}
93+
94+
/// Gets the version to which this req is precise to, if any.
95+
pub fn precise_version(&self) -> Option<&Version> {
96+
match self {
97+
OptVersionReq::Precise(version, _) => Some(version),
98+
_ => None,
99+
}
100+
}
101+
85102
pub fn is_locked(&self) -> bool {
86103
matches!(self, OptVersionReq::Locked(..))
87104
}
@@ -108,7 +125,7 @@ impl OptVersionReq {
108125
// we should not silently use `1.0.0+foo` even though they have the same version.
109126
v == version
110127
}
111-
OptVersionReq::UpdatePrecise(v, _) => {
128+
OptVersionReq::Precise(v, _) => {
112129
// This is used for the `--precise` field of cargo update.
113130
//
114131
// Unfortunately crates.io allowed versions to differ only
@@ -135,7 +152,7 @@ impl Display for OptVersionReq {
135152
OptVersionReq::Any => f.write_str("*"),
136153
OptVersionReq::Req(req)
137154
| OptVersionReq::Locked(_, req)
138-
| OptVersionReq::UpdatePrecise(_, req) => Display::fmt(req, f),
155+
| OptVersionReq::Precise(_, req) => Display::fmt(req, f),
139156
}
140157
}
141158
}

src/doc/man/cargo-update.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ Cannot be used with `--precise`.
4242
When used with _spec_, allows you to specify a specific version number to set
4343
the package to. If the package comes from a git repository, this can be a git
4444
revision (such as a SHA hash or tag).
45+
46+
While not recommended, you can specify a yanked version of a package (nightly only).
47+
When possible, try other non-yanked SemVer-compatible versions or seek help
48+
from the maintainers of the package.
4549
{{/option}}
4650

4751
{{#option "`-w`" "`--workspace`" }}

src/doc/man/generated_txt/cargo-update.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ OPTIONS
3535
to set the package to. If the package comes from a git repository,
3636
this can be a git revision (such as a SHA hash or tag).
3737

38+
While not recommended, you can specify a yanked version of a package
39+
(nightly only). When possible, try other non-yanked
40+
SemVer-compatible versions or seek help from the maintainers of the
41+
package.
42+
3843
-w, --workspace
3944
Attempt to update only packages defined in the workspace. Other
4045
packages are updated only if they don’t already exist in the

src/doc/src/commands/cargo-update.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ Cannot be used with <code>--precise</code>.</dd>
3939
<dt class="option-term" id="option-cargo-update---precise"><a class="option-anchor" href="#option-cargo-update---precise"></a><code>--precise</code> <em>precise</em></dt>
4040
<dd class="option-desc">When used with <em>spec</em>, allows you to specify a specific version number to set
4141
the package to. If the package comes from a git repository, this can be a git
42-
revision (such as a SHA hash or tag).</dd>
42+
revision (such as a SHA hash or tag).</p>
43+
<p>While not recommended, you can specify a yanked version of a package (nightly only).
44+
When possible, try other non-yanked SemVer-compatible versions or seek help
45+
from the maintainers of the package.</dd>
4346

4447

4548
<dt class="option-term" id="option-cargo-update--w"><a class="option-anchor" href="#option-cargo-update--w"></a><code>-w</code></dt>

src/doc/src/reference/resolver.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,11 @@ the `links` field if your library is in common use.
325325

326326
[Yanked releases][yank] are those that are marked that they should not be
327327
used. When the resolver is building the graph, it will ignore all yanked
328-
releases unless they already exist in the `Cargo.lock` file.
328+
releases unless they already exist in the `Cargo.lock` file or are explicitly
329+
requested by the [`--precise`] flag of `cargo update` (nightly only).
329330

330331
[yank]: publishing.md#cargo-yank
332+
[`--precise`]: ../commands/cargo-update.md#option-cargo-update---precise
331333

332334
## Dependency updates
333335

src/etc/man/cargo-update.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ Cannot be used with \fB\-\-precise\fR\&.
3939
When used with \fIspec\fR, allows you to specify a specific version number to set
4040
the package to. If the package comes from a git repository, this can be a git
4141
revision (such as a SHA hash or tag).
42+
.sp
43+
While not recommended, you can specify a yanked version of a package (nightly only).
44+
When possible, try other non\-yanked SemVer\-compatible versions or seek help
45+
from the maintainers of the package.
4246
.RE
4347
.sp
4448
\fB\-w\fR,

tests/testsuite/update.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,3 +1370,106 @@ fn update_precise_git_revisions() {
13701370
assert!(p.read_lockfile().contains(&head_id));
13711371
assert!(!p.read_lockfile().contains(&tag_commit_id));
13721372
}
1373+
1374+
#[cargo_test]
1375+
fn precise_yanked() {
1376+
Package::new("bar", "0.1.0").publish();
1377+
Package::new("bar", "0.1.1").yanked(true).publish();
1378+
let p = project()
1379+
.file(
1380+
"Cargo.toml",
1381+
r#"
1382+
[package]
1383+
name = "foo"
1384+
1385+
[dependencies]
1386+
bar = "0.1"
1387+
"#,
1388+
)
1389+
.file("src/lib.rs", "")
1390+
.build();
1391+
1392+
p.cargo("generate-lockfile").run();
1393+
1394+
// Use non-yanked version.
1395+
let lockfile = p.read_lockfile();
1396+
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\""));
1397+
1398+
p.cargo("update --precise 0.1.1 bar")
1399+
.with_status(101)
1400+
.with_stderr(
1401+
"\
1402+
[UPDATING] `dummy-registry` index
1403+
[ERROR] failed to get `bar` as a dependency of package `foo v0.0.0 ([CWD])`
1404+
1405+
Caused by:
1406+
failed to query replaced source registry `crates-io`
1407+
1408+
Caused by:
1409+
the `--precise <yanked-version>` flag is unstable[..]
1410+
See [..]
1411+
See [..]
1412+
",
1413+
)
1414+
.run();
1415+
1416+
p.cargo("update --precise 0.1.1 bar")
1417+
.masquerade_as_nightly_cargo(&["--precise <yanked-version>"])
1418+
.arg("-Zunstable-options")
1419+
.with_stderr(
1420+
"\
1421+
[UPDATING] `dummy-registry` index
1422+
[WARNING] selected package `[email protected]` was yanked by the author
1423+
[NOTE] if possible, try a compatible non-yanked version
1424+
[UPDATING] bar v0.1.0 -> v0.1.1
1425+
",
1426+
)
1427+
.run();
1428+
1429+
// Use yanked version.
1430+
let lockfile = p.read_lockfile();
1431+
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\""));
1432+
}
1433+
1434+
#[cargo_test]
1435+
fn precise_yanked_multiple_presence() {
1436+
Package::new("bar", "0.1.0").publish();
1437+
Package::new("bar", "0.1.1").yanked(true).publish();
1438+
let p = project()
1439+
.file(
1440+
"Cargo.toml",
1441+
r#"
1442+
[package]
1443+
name = "foo"
1444+
1445+
[dependencies]
1446+
bar = "0.1"
1447+
baz = { package = "bar", version = "0.1" }
1448+
"#,
1449+
)
1450+
.file("src/lib.rs", "")
1451+
.build();
1452+
1453+
p.cargo("generate-lockfile").run();
1454+
1455+
// Use non-yanked version.
1456+
let lockfile = p.read_lockfile();
1457+
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\""));
1458+
1459+
p.cargo("update --precise 0.1.1 bar")
1460+
.masquerade_as_nightly_cargo(&["--precise <yanked-version>"])
1461+
.arg("-Zunstable-options")
1462+
.with_stderr(
1463+
"\
1464+
[UPDATING] `dummy-registry` index
1465+
[WARNING] selected package `[email protected]` was yanked by the author
1466+
[NOTE] if possible, try a compatible non-yanked version
1467+
[UPDATING] bar v0.1.0 -> v0.1.1
1468+
",
1469+
)
1470+
.run();
1471+
1472+
// Use yanked version.
1473+
let lockfile = p.read_lockfile();
1474+
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\""));
1475+
}

0 commit comments

Comments
 (0)