Skip to content

Commit e9fd85a

Browse files
committed
fix: update --breaking now handles mixed renaming and pinning.
1 parent eee673d commit e9fd85a

File tree

3 files changed

+41
-27
lines changed

3 files changed

+41
-27
lines changed

src/cargo/ops/cargo_update.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ fn upgrade_dependency(
348348
return Ok(dependency);
349349
}
350350

351-
let Some(new_req_string) = upgrade_requirement(&current.to_string(), latest)? else {
351+
let Some((new_req_string, _)) = upgrade_requirement(&current.to_string(), latest)? else {
352352
trace!("skipping dependency `{name}` because the version requirement didn't change");
353353
return Ok(dependency);
354354
};
@@ -369,8 +369,17 @@ fn upgrade_dependency(
369369
Ok(dep)
370370
}
371371

372-
/// Update manifests with upgraded versions, and write to disk. Based on cargo-edit.
373-
/// Returns true if any file has changed.
372+
/// Update manifests with upgraded versions, and write to disk. Based on
373+
/// cargo-edit. Returns true if any file has changed.
374+
///
375+
/// Some of the checks here are duplicating checks already done in
376+
/// upgrade_manifests/upgrade_dependency. Why? Let's say upgrade_dependency has
377+
/// found that dependency foo was eligible for an upgrade. But foo can occur in
378+
/// multiple manifest files, and even multiple times in the same manifest file,
379+
/// and may be pinned, renamed, etc. in some of the instances. So we still need
380+
/// to check here which dependencies to actually modify. So why not drop the
381+
/// upgrade map and redo all checks here? Because then we'd have to query the
382+
/// registries again to find the latest versions.
374383
pub fn write_manifest_upgrades(
375384
ws: &Workspace<'_>,
376385
upgrades: &UpgradeMap,
@@ -407,6 +416,11 @@ pub fn write_manifest_upgrades(
407416
)?;
408417
let name = &dependency.name;
409418

419+
if let Some(renamed_to) = dependency.rename {
420+
trace!("skipping dependency renamed from `{name}` to `{renamed_to}`");
421+
continue;
422+
}
423+
410424
let Some(current) = dependency.version() else {
411425
trace!("skipping dependency without a version: {name}");
412426
continue;
@@ -424,13 +438,27 @@ pub fn write_manifest_upgrades(
424438
continue;
425439
};
426440

427-
let Some(new_req_string) = upgrade_requirement(current, latest)? else {
441+
let Some((new_req_string, new_req)) = upgrade_requirement(current, latest)? else {
428442
trace!(
429443
"skipping dependency `{name}` because the version requirement didn't change"
430444
);
431445
continue;
432446
};
433447

448+
let [comparator] = &new_req.comparators[..] else {
449+
trace!(
450+
"skipping dependency `{}` with multiple version comparators: {:?}",
451+
name,
452+
new_req.comparators
453+
);
454+
continue;
455+
};
456+
457+
if comparator.op != Op::Caret {
458+
trace!("skipping non-caret dependency `{}`: {}", name, comparator);
459+
continue;
460+
}
461+
434462
let mut dep = dependency.clone();
435463
let mut source = source.clone();
436464
source.version = new_req_string;

src/cargo/util/toml_mut/upgrade.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::CargoResult;
77
pub(crate) fn upgrade_requirement(
88
req: &str,
99
version: &semver::Version,
10-
) -> CargoResult<Option<String>> {
10+
) -> CargoResult<Option<(String, semver::VersionReq)>> {
1111
let req_text = req.to_string();
1212
let raw_req = semver::VersionReq::parse(&req_text)
1313
.expect("semver to generate valid version requirements");
@@ -40,7 +40,7 @@ pub(crate) fn upgrade_requirement(
4040
if new_req_text == req_text {
4141
Ok(None)
4242
} else {
43-
Ok(Some(new_req_text))
43+
Ok(Some((new_req_text, new_req)))
4444
}
4545
}
4646
}
@@ -103,7 +103,9 @@ mod test {
103103
#[track_caller]
104104
fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
105105
let version = semver::Version::parse(version).unwrap();
106-
let actual = upgrade_requirement(req, &version).unwrap();
106+
let actual = upgrade_requirement(req, &version)
107+
.unwrap()
108+
.map(|(actual, _req)| actual);
107109
let expected = expected.into();
108110
assert_eq!(actual.as_deref(), expected);
109111
}

tests/testsuite/update.rs

+4-20
Original file line numberDiff line numberDiff line change
@@ -2460,19 +2460,6 @@ fn update_breaking_mixed_compatibility() {
24602460
}
24612461

24622462
#[cargo_test]
2463-
/// In order to handle cases like this, it's not enough to keep an upgrade map
2464-
/// keyed on dependency name and source, we also need to know which manifest
2465-
/// files to update and which to keep unchanged, because they may be pinned or
2466-
/// renamed in some but not others. But we don't know which manifest file a
2467-
/// pinned/renamed dependency comes from. It might come from the workspace root
2468-
/// manifest. (We could potentially find that out in or around
2469-
/// to_real_manifest.)
2470-
///
2471-
/// Instead, when writing manifest changes, we will have to check if a
2472-
/// dependency is pinned, renamed, etc., and should be skipped. This is
2473-
/// unfortunate, as it means that ops::write_manifest_upgrades is duplicating
2474-
/// some of the logic in ops::upgrade_manifests, and that is a potential source
2475-
/// of bugs.
24762463
fn update_breaking_mixed_pinning_renaming() {
24772464
Package::new("mixed-pinned", "1.0.0").publish();
24782465
Package::new("mixed-ws-pinned", "1.0.0").publish();
@@ -2564,21 +2551,19 @@ fn update_breaking_mixed_pinning_renaming() {
25642551
let root_manifest = p.read_file("Cargo.toml");
25652552
assert_e2e().eq(
25662553
&root_manifest,
2567-
// FIXME: The pinned dependency should not be upgraded.
25682554
str![[r#"
25692555
25702556
[workspace]
25712557
members = ["pinned", "unpinned", "mixed"]
25722558
25732559
[workspace.dependencies]
2574-
mixed-ws-pinned = "=2.0"
2560+
mixed-ws-pinned = "=1.0"
25752561
"#]],
25762562
);
25772563

25782564
let pinned_manifest = p.read_file("pinned/Cargo.toml");
25792565
assert_e2e().eq(
25802566
&pinned_manifest,
2581-
// FIXME: The pinned and renamed dependencies should not be upgraded.
25822567
str![[r#"
25832568
25842569
[package]
@@ -2588,9 +2573,9 @@ fn update_breaking_mixed_pinning_renaming() {
25882573
authors = []
25892574
25902575
[dependencies]
2591-
mixed-pinned = "=2.0"
2576+
mixed-pinned = "=1.0"
25922577
mixed-ws-pinned.workspace = true
2593-
renamed-to = { package = "renamed-from", version = "2.0" }
2578+
renamed-to = { package = "renamed-from", version = "1.0" }
25942579
"#]],
25952580
);
25962581

@@ -2615,7 +2600,6 @@ fn update_breaking_mixed_pinning_renaming() {
26152600
let mixed_manifest = p.read_file("mixed/Cargo.toml");
26162601
assert_e2e().eq(
26172602
&mixed_manifest,
2618-
// FIXME: The pinned dependency should not be upgraded.
26192603
str![[r#"
26202604
26212605
[package]
@@ -2628,7 +2612,7 @@ fn update_breaking_mixed_pinning_renaming() {
26282612
mixed-pinned = "2.0"
26292613
26302614
[target.'cfg(unix)'.dependencies]
2631-
mixed-pinned = "=2.0"
2615+
mixed-pinned = "=1.0"
26322616
"#]],
26332617
);
26342618
}

0 commit comments

Comments
 (0)