Skip to content

Commit 5cb1f15

Browse files
committed
fix: Use ops::update_lockfile for consistency with non-breaking update.
1 parent ef1656c commit 5cb1f15

File tree

3 files changed

+108
-23
lines changed

3 files changed

+108
-23
lines changed

src/bin/cargo/commands/update.rs

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use std::collections::HashMap;
2+
13
use crate::command_prelude::*;
24

35
use anyhow::anyhow;
46
use cargo::ops::{self, UpdateOptions};
57
use cargo::util::print_available_packages;
8+
use tracing::trace;
69

710
pub fn cli() -> Command {
811
subcommand("update")
@@ -92,28 +95,38 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
9295
let update_opts = UpdateOptions {
9396
recursive: args.flag("recursive"),
9497
precise: args.get_one::<String>("precise").map(String::as_str),
98+
breaking: args.flag("breaking"),
9599
to_update,
96100
dry_run: args.dry_run(),
97101
workspace: args.flag("workspace"),
98102
gctx,
99103
};
100104

101-
if args.flag("breaking") {
102-
gctx.cli_unstable()
103-
.fail_if_stable_opt("--breaking", 12425)?;
104-
105-
let upgrades = ops::upgrade_manifests(&mut ws, &update_opts.to_update)?;
106-
ops::resolve_ws(&ws, update_opts.dry_run)?;
107-
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
105+
let breaking_update = update_opts.breaking; // or if doing a breaking precise update, coming in #14140.
108106

109-
if update_opts.dry_run {
110-
update_opts
111-
.gctx
112-
.shell()
113-
.warn("aborting update due to dry run")?;
107+
// We are using the term "upgrade" here, which is the typical case, but it
108+
// can also be a downgrade (in the case of a precise update). In general, it
109+
// is a change to a version req matching a precise version.
110+
let upgrades = if breaking_update {
111+
if update_opts.breaking {
112+
gctx.cli_unstable()
113+
.fail_if_stable_opt("--breaking", 12425)?;
114114
}
115+
116+
trace!("allowing breaking updates");
117+
ops::upgrade_manifests(&mut ws, &update_opts.to_update)?
115118
} else {
116-
ops::update_lockfile(&ws, &update_opts)?;
119+
HashMap::new()
120+
};
121+
122+
ops::update_lockfile(&ws, &update_opts, &upgrades)?;
123+
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
124+
125+
if update_opts.dry_run {
126+
update_opts
127+
.gctx
128+
.shell()
129+
.warn("aborting update due to dry run")?;
117130
}
118131

119132
Ok(())

src/cargo/ops/cargo_update.rs

+28-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::ops;
99
use crate::sources::source::QueryKind;
1010
use crate::util::cache_lock::CacheLockMode;
1111
use crate::util::context::GlobalContext;
12+
use crate::util::interning::InternedString;
1213
use crate::util::toml_mut::dependency::{MaybeWorkspace, Source};
1314
use crate::util::toml_mut::manifest::LocalManifest;
1415
use crate::util::toml_mut::upgrade::upgrade_requirement;
@@ -20,12 +21,24 @@ use std::cmp::Ordering;
2021
use std::collections::{BTreeMap, HashMap, HashSet};
2122
use tracing::{debug, trace};
2223

23-
pub type UpgradeMap = HashMap<(String, SourceId), Version>;
24+
/// A map of all breaking upgrades which is filled in by
25+
/// upgrade_manifests/upgrade_dependency when going through workspace member
26+
/// manifests, and later used by write_manifest_upgrades in order to know which
27+
/// upgrades to write to manifest files on disk. Also used by update_lockfile to
28+
/// know which dependencies to keep unchanged if any have been upgraded (we will
29+
/// do either breaking or non-breaking updates, but not both).
30+
pub type UpgradeMap = HashMap<
31+
// The key is a tuple of the package name and the source id of the package.
32+
(InternedString, SourceId),
33+
// The value is the upgraded version of the package.
34+
Version,
35+
>;
2436

2537
pub struct UpdateOptions<'a> {
2638
pub gctx: &'a GlobalContext,
2739
pub to_update: Vec<String>,
2840
pub precise: Option<&'a str>,
41+
pub breaking: bool,
2942
pub recursive: bool,
3043
pub dry_run: bool,
3144
pub workspace: bool,
@@ -49,7 +62,11 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
4962
Ok(())
5063
}
5164

52-
pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
65+
pub fn update_lockfile(
66+
ws: &Workspace<'_>,
67+
opts: &UpdateOptions<'_>,
68+
upgrades: &UpgradeMap,
69+
) -> CargoResult<()> {
5370
if opts.recursive && opts.precise.is_some() {
5471
anyhow::bail!("cannot specify both recursive and precise simultaneously")
5572
}
@@ -165,7 +182,13 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
165182
.filter(|s| !s.is_registry())
166183
.collect();
167184

168-
let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
185+
let keep = |p: &PackageId| {
186+
(!to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p))
187+
// In case of `--breaking` (or precise upgrades, coming in
188+
// #14140), we want to keep all packages unchanged that didn't
189+
// get upgraded.
190+
|| (opts.breaking && !upgrades.contains_key(&(p.name(), p.source_id())))
191+
};
169192

170193
let mut resolve = ops::resolve_with_previous(
171194
&mut registry,
@@ -361,7 +384,7 @@ fn upgrade_dependency(
361384
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
362385
}
363386

364-
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
387+
upgrades.insert((name, dependency.source_id()), latest.clone());
365388

366389
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
367390
let mut dep = dependency.clone();
@@ -433,7 +456,7 @@ pub fn write_manifest_upgrades(
433456
continue;
434457
};
435458

436-
let Some(latest) = upgrades.get(&(name.to_owned(), source_id)) else {
459+
let Some(latest) = upgrades.get(&(name.into(), source_id)) else {
437460
trace!("skipping dependency without an upgrade: {name}");
438461
continue;
439462
};

tests/testsuite/update.rs

+54-5
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ fn dry_run_update() {
926926
[UPDATING] serde v0.1.0 -> v0.1.1
927927
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
928928
[WARNING] not updating lockfile due to dry run
929+
[WARNING] aborting update due to dry run
929930
930931
"#]])
931932
.run();
@@ -1525,6 +1526,7 @@ fn report_behind() {
15251526
[UPDATING] breaking v0.1.0 -> v0.1.1 (latest: v0.2.0)
15261527
[NOTE] pass `--verbose` to see 2 unchanged dependencies behind latest
15271528
[WARNING] not updating lockfile due to dry run
1529+
[WARNING] aborting update due to dry run
15281530
15291531
"#]])
15301532
.run();
@@ -1538,6 +1540,7 @@ fn report_behind() {
15381540
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15391541
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
15401542
[WARNING] not updating lockfile due to dry run
1543+
[WARNING] aborting update due to dry run
15411544
15421545
"#]])
15431546
.run();
@@ -1550,6 +1553,7 @@ fn report_behind() {
15501553
[LOCKING] 0 packages to latest compatible versions
15511554
[NOTE] pass `--verbose` to see 3 unchanged dependencies behind latest
15521555
[WARNING] not updating lockfile due to dry run
1556+
[WARNING] aborting update due to dry run
15531557
15541558
"#]])
15551559
.run();
@@ -1563,6 +1567,7 @@ fn report_behind() {
15631567
[UNCHANGED] two-ver v0.1.0 (latest: v0.2.0)
15641568
[NOTE] to see how you depend on a package, run `cargo tree --invert --package <dep>@<ver>`
15651569
[WARNING] not updating lockfile due to dry run
1570+
[WARNING] aborting update due to dry run
15661571
15671572
"#]])
15681573
.run();
@@ -1695,6 +1700,7 @@ fn update_breaking_dry_run() {
16951700
[LOCKING] 2 packages to latest compatible versions
16961701
[UPDATING] incompatible v1.0.0 -> v2.0.0
16971702
[UPDATING] ws v1.0.0 -> v2.0.0
1703+
[WARNING] not updating lockfile due to dry run
16981704
[WARNING] aborting update due to dry run
16991705
17001706
"#]])
@@ -1912,10 +1918,13 @@ fn update_breaking() {
19121918
[UPDATING] multiple-registries v2.0.0 (registry `alternative`) -> v3.0.0
19131919
[UPDATING] multiple-registries v1.0.0 -> v2.0.0
19141920
[UPDATING] multiple-source-types v1.0.0 -> v2.0.0
1921+
[REMOVING] multiple-versions v1.0.0
1922+
[REMOVING] multiple-versions v2.0.0
19151923
[ADDING] multiple-versions v3.0.0
19161924
[UPDATING] platform-specific v1.0.0 -> v2.0.0
19171925
[UPDATING] shared v1.0.0 -> v2.0.0
19181926
[UPDATING] ws v1.0.0 -> v2.0.0
1927+
[NOTE] pass `--verbose` to see 4 unchanged dependencies behind latest
19191928
19201929
"#]])
19211930
.run();
@@ -2108,6 +2117,7 @@ fn update_breaking_specific_packages() {
21082117
[UPDATING] transitive-compatible v1.0.0 -> v1.0.1
21092118
[UPDATING] transitive-incompatible v1.0.0 -> v2.0.0
21102119
[UPDATING] ws v1.0.0 -> v2.0.0
2120+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
21112121
21122122
"#]])
21132123
.run();
@@ -2163,6 +2173,8 @@ fn update_breaking_specific_packages_that_wont_update() {
21632173
.masquerade_as_nightly_cargo(&["update-breaking"])
21642174
.with_stderr_data(str![[r#"
21652175
[UPDATING] `dummy-registry` index
2176+
[LOCKING] 0 packages to latest compatible versions
2177+
[NOTE] pass `--verbose` to see 5 unchanged dependencies behind latest
21662178
21672179
"#]])
21682180
.run();
@@ -2271,14 +2283,27 @@ fn update_breaking_spec_version() {
22712283
// Spec version not matching our current dependencies
22722284
p.cargo("update -Zunstable-options --breaking [email protected]")
22732285
.masquerade_as_nightly_cargo(&["update-breaking"])
2274-
.with_stderr_data(str![[r#""#]])
2286+
.with_status(101)
2287+
.with_stderr_data(str![[r#"
2288+
[ERROR] package ID specification `[email protected]` did not match any packages
2289+
Did you mean one of these?
2290+
2291+
2292+
2293+
"#]])
22752294
.run();
22762295

22772296
// Spec source not matching our current dependencies
22782297
p.cargo("update -Zunstable-options --breaking https://alternative.com#[email protected]")
22792298
.masquerade_as_nightly_cargo(&["update-breaking"])
2280-
.with_stderr_data(str![[r#""#]])
2281-
.run();
2299+
.with_status(101)
2300+
.with_stderr_data(str![[r#"
2301+
[ERROR] package ID specification `https://alternative.com/#[email protected]` did not match any packages
2302+
Did you mean one of these?
2303+
2304+
2305+
2306+
"#]]) .run();
22822307

22832308
// Accepted spec
22842309
p.cargo("update -Zunstable-options --breaking [email protected]")
@@ -2288,6 +2313,7 @@ fn update_breaking_spec_version() {
22882313
[UPGRADING] incompatible ^1.0 -> ^2.0
22892314
[LOCKING] 1 package to latest compatible version
22902315
[UPDATING] incompatible v1.0.0 -> v2.0.0
2316+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
22912317
22922318
"#]])
22932319
.run();
@@ -2301,6 +2327,7 @@ fn update_breaking_spec_version() {
23012327
[UPGRADING] incompatible ^2.0 -> ^3.0
23022328
[LOCKING] 1 package to latest compatible version
23032329
[UPDATING] incompatible v2.0.0 -> v3.0.0
2330+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23042331
23052332
"#]])
23062333
.run();
@@ -2310,19 +2337,35 @@ fn update_breaking_spec_version() {
23102337
.masquerade_as_nightly_cargo(&["update-breaking"])
23112338
.with_stderr_data(str![[r#"
23122339
[UPDATING] `dummy-registry` index
2340+
[LOCKING] 0 packages to latest compatible versions
2341+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23132342
23142343
"#]])
23152344
.run();
23162345

23172346
// Non-existing versions
23182347
p.cargo("update -Zunstable-options --breaking [email protected]")
23192348
.masquerade_as_nightly_cargo(&["update-breaking"])
2320-
.with_stderr_data(str![[r#""#]])
2349+
.with_status(101)
2350+
.with_stderr_data(str![[r#"
2351+
[ERROR] package ID specification `[email protected]` did not match any packages
2352+
Did you mean one of these?
2353+
2354+
2355+
2356+
"#]])
23212357
.run();
23222358

23232359
p.cargo("update -Zunstable-options --breaking [email protected]")
23242360
.masquerade_as_nightly_cargo(&["update-breaking"])
2325-
.with_stderr_data(str![[r#""#]])
2361+
.with_status(101)
2362+
.with_stderr_data(str![[r#"
2363+
[ERROR] package ID specification `[email protected]` did not match any packages
2364+
Did you mean one of these?
2365+
2366+
2367+
2368+
"#]])
23262369
.run();
23272370
}
23282371

@@ -2376,6 +2419,7 @@ fn update_breaking_spec_version_transitive() {
23762419
[UPGRADING] dep ^1.0 -> ^3.0
23772420
[LOCKING] 1 package to latest compatible version
23782421
[UPDATING] dep v1.0.0 -> v3.0.0
2422+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23792423
23802424
"#]])
23812425
.run();
@@ -2385,6 +2429,8 @@ fn update_breaking_spec_version_transitive() {
23852429
.masquerade_as_nightly_cargo(&["update-breaking"])
23862430
.with_stderr_data(str![[r#"
23872431
[UPDATING] `dummy-registry` index
2432+
[LOCKING] 0 packages to latest compatible versions
2433+
[NOTE] pass `--verbose` to see 1 unchanged dependencies behind latest
23882434
23892435
"#]])
23902436
.run();
@@ -2453,6 +2499,8 @@ fn update_breaking_mixed_compatibility() {
24532499
[UPDATING] `dummy-registry` index
24542500
[UPGRADING] mixed-compatibility ^1.0 -> ^2.0
24552501
[LOCKING] 1 package to latest compatible version
2502+
[REMOVING] mixed-compatibility v1.0.0
2503+
[REMOVING] mixed-compatibility v2.0.0
24562504
[ADDING] mixed-compatibility v2.0.1
24572505
24582506
"#]])
@@ -2544,6 +2592,7 @@ fn update_breaking_mixed_pinning_renaming() {
25442592
[ADDING] mixed-pinned v2.0.0
25452593
[ADDING] mixed-ws-pinned v2.0.0
25462594
[ADDING] renamed-from v2.0.0
2595+
[NOTE] pass `--verbose` to see 3 unchanged dependencies behind latest
25472596
25482597
"#]])
25492598
.run();

0 commit comments

Comments
 (0)