Skip to content

Commit 5039549

Browse files
committed
feat: Make cargo update --precise capable of doing breaking upgrades.
1 parent 986e783 commit 5039549

File tree

6 files changed

+267
-151
lines changed

6 files changed

+267
-151
lines changed

crates/cargo-test-support/src/compare.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
231231
("[CREATED]", " Created"),
232232
("[CREATING]", " Creating"),
233233
("[CREDENTIAL]", " Credential"),
234+
("[UPGRADING]", " Upgrading"),
234235
("[DOWNGRADING]", " Downgrading"),
235236
("[FINISHED]", " Finished"),
236237
("[ERROR]", "error:"),
@@ -243,7 +244,6 @@ static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
243244
("[DIRTY]", " Dirty"),
244245
("[LOCKING]", " Locking"),
245246
("[UPDATING]", " Updating"),
246-
("[UPGRADING]", " Upgrading"),
247247
("[ADDING]", " Adding"),
248248
("[REMOVING]", " Removing"),
249249
("[REMOVED]", " Removed"),

src/bin/cargo/commands/update.rs

Lines changed: 27 additions & 13 deletions
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,39 @@ 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
106+
|| (update_opts.precise.is_some() && gctx.cli_unstable().update_precise_breaking);
108107

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

119133
Ok(())

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,7 @@ unstable_cli_options!(
786786
target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
787787
trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
788788
unstable_options: bool = ("Allow the usage of unstable options"),
789+
update_precise_breaking: bool = ("Allow `update --precise` to do breaking upgrades"),
789790
);
790791

791792
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
@@ -1288,6 +1289,7 @@ impl CliUnstable {
12881289
"script" => self.script = parse_empty(k, v)?,
12891290
"target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
12901291
"unstable-options" => self.unstable_options = parse_empty(k, v)?,
1292+
"update-precise-breaking" => self.update_precise_breaking = parse_empty(k, v)?,
12911293
_ => bail!("\
12921294
unknown `-Z` flag specified: {k}\n\n\
12931295
For available unstable features, see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html\n\

src/cargo/ops/cargo_update.rs

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::util::toml_mut::manifest::LocalManifest;
1414
use crate::util::toml_mut::upgrade::upgrade_requirement;
1515
use crate::util::{style, OptVersionReq};
1616
use crate::util::{CargoResult, VersionExt};
17+
use anyhow::Context;
1718
use itertools::Itertools;
1819
use semver::{Op, Version, VersionReq};
1920
use std::cmp::Ordering;
@@ -26,6 +27,7 @@ pub struct UpdateOptions<'a> {
2627
pub gctx: &'a GlobalContext,
2728
pub to_update: Vec<String>,
2829
pub precise: Option<&'a str>,
30+
pub breaking: bool,
2931
pub recursive: bool,
3032
pub dry_run: bool,
3133
pub workspace: bool,
@@ -49,7 +51,11 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
4951
Ok(())
5052
}
5153

52-
pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoResult<()> {
54+
pub fn update_lockfile(
55+
ws: &Workspace<'_>,
56+
opts: &UpdateOptions<'_>,
57+
upgrades: &UpgradeMap,
58+
) -> CargoResult<()> {
5359
if opts.recursive && opts.precise.is_some() {
5460
anyhow::bail!("cannot specify both recursive and precise simultaneously")
5561
}
@@ -165,7 +171,12 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
165171
.filter(|s| !s.is_registry())
166172
.collect();
167173

168-
let keep = |p: &PackageId| !to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p);
174+
let keep = |p: &PackageId| {
175+
(!to_avoid_sources.contains(&p.source_id()) && !to_avoid.contains(p))
176+
// In case of `--breaking`, we want to keep all packages unchanged that
177+
// didn't get upgraded.
178+
|| (opts.breaking && !upgrades.contains_key(&(p.name().to_string(), p.source_id())))
179+
};
169180

170181
let mut resolve = ops::resolve_with_previous(
171182
&mut registry,
@@ -185,11 +196,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
185196
opts.precise.is_some(),
186197
&mut registry,
187198
)?;
188-
if opts.dry_run {
189-
opts.gctx
190-
.shell()
191-
.warn("not updating lockfile due to dry run")?;
192-
} else {
199+
if !opts.dry_run {
193200
ops::write_pkg_lockfile(ws, &mut resolve)?;
194201
}
195202
Ok(())
@@ -217,6 +224,7 @@ pub fn print_lockfile_changes(
217224
pub fn upgrade_manifests(
218225
ws: &mut Workspace<'_>,
219226
to_update: &Vec<String>,
227+
precise: &Option<&str>,
220228
) -> CargoResult<UpgradeMap> {
221229
let gctx = ws.gctx();
222230
let mut upgrades = HashMap::new();
@@ -245,6 +253,7 @@ pub fn upgrade_manifests(
245253
upgrade_dependency(
246254
&gctx,
247255
&to_update,
256+
precise,
248257
&mut registry,
249258
&mut upgrades,
250259
&mut upgrade_messages,
@@ -259,6 +268,7 @@ pub fn upgrade_manifests(
259268
fn upgrade_dependency(
260269
gctx: &GlobalContext,
261270
to_update: &Vec<PackageIdSpec>,
271+
precise: &Option<&str>,
262272
registry: &mut PackageRegistry<'_>,
263273
upgrades: &mut UpgradeMap,
264274
upgrade_messages: &mut HashSet<String>,
@@ -316,7 +326,7 @@ fn upgrade_dependency(
316326
let query =
317327
crate::core::dependency::Dependency::parse(name, None, dependency.source_id().clone())?;
318328

319-
let possibilities = {
329+
let possibilities = if precise.is_none() {
320330
loop {
321331
match registry.query_vec(&query, QueryKind::Exact) {
322332
std::task::Poll::Ready(res) => {
@@ -325,6 +335,8 @@ fn upgrade_dependency(
325335
std::task::Poll::Pending => registry.block_until_ready()?,
326336
}
327337
}
338+
} else {
339+
Vec::new()
328340
};
329341

330342
let latest = if !possibilities.is_empty() {
@@ -338,32 +350,60 @@ fn upgrade_dependency(
338350
None
339351
};
340352

341-
let Some(latest) = latest else {
353+
let new_version = if let Some(precise) = precise {
354+
Version::parse(precise)
355+
.with_context(|| format!("invalid version format for precise version `{precise}`"))?
356+
} else if let Some(latest) = latest {
357+
latest.clone()
358+
} else {
359+
// Breaking (not precise) upgrade did not find a latest version
342360
trace!("skipping dependency `{name}` without any published versions");
343361
return Ok(dependency);
344362
};
345363

346-
if current.matches(&latest) {
364+
if current.matches(&new_version) {
347365
trace!("skipping dependency `{name}` without a breaking update available");
348366
return Ok(dependency);
349367
}
350368

351-
let Some((new_req_string, _)) = upgrade_requirement(&current.to_string(), latest)? else {
369+
let Some((new_req_string, _)) = upgrade_requirement(&current.to_string(), &new_version)? else {
352370
trace!("skipping dependency `{name}` because the version requirement didn't change");
353371
return Ok(dependency);
354372
};
355373

356374
let upgrade_message = format!("{name} {current} -> {new_req_string}");
357375
trace!(upgrade_message);
358376

377+
let old_version = semver::Version::new(
378+
comparator.major,
379+
comparator.minor.unwrap_or_default(),
380+
comparator.patch.unwrap_or_default(),
381+
);
382+
let is_downgrade = new_version < old_version;
383+
let status = if is_downgrade {
384+
"Downgrading"
385+
} else {
386+
"Upgrading"
387+
};
388+
359389
if upgrade_messages.insert(upgrade_message.clone()) {
360390
gctx.shell()
361-
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
391+
.status_with_color(status, &upgrade_message, &style::WARN)?;
362392
}
363393

364-
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
394+
upgrades.insert(
395+
(name.to_string(), dependency.source_id()),
396+
new_version.clone(),
397+
);
398+
399+
let new_version_req = VersionReq::parse(&new_version.to_string())?;
400+
401+
let req = if precise.is_some() {
402+
OptVersionReq::Precise(new_version, new_version_req)
403+
} else {
404+
OptVersionReq::Req(new_version_req)
405+
};
365406

366-
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
367407
let mut dep = dependency.clone();
368408
dep.set_version_req(req);
369409
Ok(dep)

tests/testsuite/cargo/z_help/stdout.term.svg

Lines changed: 8 additions & 6 deletions
Loading

0 commit comments

Comments
 (0)