aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/commands/update.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-02 11:31:59 +0900
committernsfisis <nsfisis@gmail.com>2026-05-02 11:31:59 +0900
commiteef83859937cfa140131636f134104cf3549cf5c (patch)
tree0f1538fa4ecda9dc4e41a62875173efc1a45ac03 /crates/mozart/src/commands/update.rs
parentb216124157b69edc7330291214a6043c555f0ade (diff)
downloadphp-mozart-eef83859937cfa140131636f134104cf3549cf5c.tar.gz
php-mozart-eef83859937cfa140131636f134104cf3549cf5c.tar.zst
php-mozart-eef83859937cfa140131636f134104cf3549cf5c.zip
fix(update): normalize locked version to 4-segment form on partial pin
`apply_partial_update` and `apply_patch_only` both pinned non-listed packages back to the lock by copying `LockedPackage.version_normalized` verbatim, falling back to the raw pretty `version` when the field was missing. Lock files written by Composer always include the field, but hand-written fixtures (every `--LOCK--` block in the installer fixtures, in particular) typically only carry `version`. The 3-segment form ("1.0.0") then leaked into the resolved package, where `LockFileGenerationRequest::inline_lookup` compares against the 4-segment normalizer output ("1.0.0.0") and missed inline `type: package` entries — triggering a Packagist fetch (and proxy-blocked failure under the test harness) for a package that should never need one. Extract a single `locked_version_normalized` helper that runs the pretty version through `mozart_semver::Version::parse(...).to_string()` when the lock omits `version_normalized`, and use it from both call sites. Mirrors `packagist_to_pool_inputs` and `inline_lookup`, which already produce the 4-segment form. Unblocks 26 installer fixtures: the entire update-allow-list cluster (16, minus the alias subcase), five partial-update cases, and five others (full-update-minimal-changes, load-replaced-package-if-replacer-dropped, remove-deletes-unused-deps, remove-does-nothing-if-removal-requires-update-of-dep, update-changes-url). Scoreboard: 107 → 133 of 187 installer fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/commands/update.rs')
-rw-r--r--crates/mozart/src/commands/update.rs33
1 files changed, 23 insertions, 10 deletions
diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs
index 3a468d4..b58155e 100644
--- a/crates/mozart/src/commands/update.rs
+++ b/crates/mozart/src/commands/update.rs
@@ -267,6 +267,25 @@ pub fn compute_update_changes(
// Helper: apply partial update filter
// ─────────────────────────────────────────────────────────────────────────────
+/// Resolve a `LockedPackage`'s normalized version, falling back to the
+/// canonical 4-segment form derived from the pretty version when the lock
+/// omits `version_normalized`.
+///
+/// Lock files written by Composer always include the field, but hand-written
+/// fixtures (and `.test` LOCK sections) often only carry `version`. Returning
+/// the raw pretty version here would break downstream consumers that compare
+/// against `mozart_semver::Version::to_string()` output — most importantly
+/// `lockfile::LockFileGenerationRequest::inline_lookup`, which would then miss
+/// inline `type: package` entries on partial updates and trigger a Packagist
+/// fetch for a package that should never need one.
+fn locked_version_normalized(pkg: &lockfile::LockedPackage) -> String {
+ pkg.version_normalized.clone().unwrap_or_else(|| {
+ mozart_semver::Version::parse(&pkg.version)
+ .map(|v| v.to_string())
+ .unwrap_or_else(|_| pkg.version.clone())
+ })
+}
+
/// For a partial update (when specific packages are named on the CLI), swap back
/// the versions of packages that were NOT requested to be updated.
///
@@ -307,10 +326,7 @@ pub fn apply_partial_update(
&& let Some(old_pkg) = old_pkg_map.get(&name_lower)
{
pkg.version = old_pkg.version.clone();
- pkg.version_normalized = old_pkg
- .version_normalized
- .clone()
- .unwrap_or_else(|| old_pkg.version.clone());
+ pkg.version_normalized = locked_version_normalized(old_pkg);
pkg.is_dev = false; // preserve existing; lock file doesn't store this flag directly
}
pkg
@@ -664,18 +680,15 @@ pub fn apply_patch_only(
.map(|mut pkg| {
let name_lower = pkg.name.to_lowercase();
if let Some(old_pkg) = old_pkg_map.get(&name_lower) {
- let old_norm = old_pkg
- .version_normalized
- .as_deref()
- .unwrap_or(&old_pkg.version);
+ let old_norm = locked_version_normalized(old_pkg);
let new_norm = &pkg.version_normalized;
// Compare major.minor: if they differ, pin to old version
- let old_mm = major_minor(old_norm);
+ let old_mm = major_minor(&old_norm);
let new_mm = major_minor(new_norm);
if old_mm != new_mm {
pkg.version = old_pkg.version.clone();
- pkg.version_normalized = old_norm.to_string();
+ pkg.version_normalized = old_norm;
}
}
pkg