aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart
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
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')
-rw-r--r--crates/mozart/src/commands/update.rs33
-rw-r--r--crates/mozart/tests/installer.rs130
2 files changed, 49 insertions, 114 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
diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs
index 3c47750..beeb8de 100644
--- a/crates/mozart/tests/installer.rs
+++ b/crates/mozart/tests/installer.rs
@@ -144,10 +144,7 @@ installer_fixture!(
);
installer_fixture!(deduplicate_solver_problems);
installer_fixture!(disjunctive_multi_constraints);
-installer_fixture!(
- full_update_minimal_changes,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(full_update_minimal_changes);
installer_fixture!(github_issues_4319);
installer_fixture!(github_issues_4795);
installer_fixture!(github_issues_4795_2);
@@ -210,10 +207,7 @@ installer_fixture!(
installer_fixture!(install_self_from_root);
installer_fixture!(install_simple);
installer_fixture!(install_without_lock);
-installer_fixture!(
- load_replaced_package_if_replacer_dropped,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(load_replaced_package_if_replacer_dropped);
installer_fixture!(outdated_lock_file_fails_install);
installer_fixture!(outdated_lock_file_with_new_platform_reqs_fails);
installer_fixture!(
@@ -225,20 +219,11 @@ installer_fixture!(
ignore = "mozart binary cannot yet run this fixture"
);
installer_fixture!(partial_update_forces_dev_reference_from_lock_for_non_updated_packages);
-installer_fixture!(
- partial_update_from_lock,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(partial_update_from_lock);
installer_fixture!(partial_update_from_lock_with_root_alias);
installer_fixture!(partial_update_installs_from_lock_even_missing);
-installer_fixture!(
- partial_update_keeps_older_dep_if_still_required,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- partial_update_keeps_older_dep_if_still_required_with_provide,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(partial_update_keeps_older_dep_if_still_required);
+installer_fixture!(partial_update_keeps_older_dep_if_still_required_with_provide);
installer_fixture!(
partial_update_loads_root_aliases_for_path_repos,
ignore = "mozart binary cannot yet run this fixture"
@@ -251,14 +236,8 @@ installer_fixture!(
partial_update_security_advisory_matching_locked_dep_with_dependencies,
ignore = "mozart binary cannot yet run this fixture"
);
-installer_fixture!(
- partial_update_with_dependencies_provide,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- partial_update_with_dependencies_replace,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(partial_update_with_dependencies_provide);
+installer_fixture!(partial_update_with_dependencies_replace);
installer_fixture!(
partial_update_with_deps_warns_root,
ignore = "mozart binary cannot yet run this fixture"
@@ -299,14 +278,8 @@ installer_fixture!(
ignore = "mozart binary cannot yet run this fixture"
);
installer_fixture!(provider_satisfies_its_own_requirement);
-installer_fixture!(
- remove_deletes_unused_deps,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- remove_does_nothing_if_removal_requires_update_of_dep,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(remove_deletes_unused_deps);
+installer_fixture!(remove_does_nothing_if_removal_requires_update_of_dep);
installer_fixture!(
replace_alias,
ignore = "mozart binary cannot yet run this fixture"
@@ -372,80 +345,29 @@ installer_fixture!(update_alias_lock);
installer_fixture!(update_alias_lock2);
installer_fixture!(update_all);
installer_fixture!(update_all_dry_run);
-installer_fixture!(
- update_allow_list,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_locked_require,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_minimal_changes,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(update_allow_list);
+installer_fixture!(update_allow_list_locked_require);
+installer_fixture!(update_allow_list_minimal_changes);
installer_fixture!(update_allow_list_patterns);
-installer_fixture!(
- update_allow_list_patterns_with_all_dependencies,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_patterns_with_dependencies,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_patterns_with_root_dependencies,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_patterns_without_dependencies,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_reads_lock,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(update_allow_list_patterns_with_all_dependencies);
+installer_fixture!(update_allow_list_patterns_with_dependencies);
+installer_fixture!(update_allow_list_patterns_with_root_dependencies);
+installer_fixture!(update_allow_list_patterns_without_dependencies);
+installer_fixture!(update_allow_list_reads_lock);
installer_fixture!(update_allow_list_removes_unused);
-installer_fixture!(
- update_allow_list_require_new_replace,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_warns_non_existing_patterns,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_with_dependencies,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(update_allow_list_require_new_replace);
+installer_fixture!(update_allow_list_warns_non_existing_patterns);
+installer_fixture!(update_allow_list_with_dependencies);
installer_fixture!(
update_allow_list_with_dependencies_alias,
ignore = "mozart binary cannot yet run this fixture"
);
-installer_fixture!(
- update_allow_list_with_dependencies_new_requirement,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_with_dependencies_require_new,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_with_dependencies_require_new_replace,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_with_dependencies_require_new_replace_mutual,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_allow_list_with_dependency_conflict,
- ignore = "mozart binary cannot yet run this fixture"
-);
-installer_fixture!(
- update_changes_url,
- ignore = "mozart binary cannot yet run this fixture"
-);
+installer_fixture!(update_allow_list_with_dependencies_new_requirement);
+installer_fixture!(update_allow_list_with_dependencies_require_new);
+installer_fixture!(update_allow_list_with_dependencies_require_new_replace);
+installer_fixture!(update_allow_list_with_dependencies_require_new_replace_mutual);
+installer_fixture!(update_allow_list_with_dependency_conflict);
+installer_fixture!(update_changes_url);
installer_fixture!(update_dev_ignores_providers);
installer_fixture!(update_dev_packages_updates_repo_url);
installer_fixture!(update_dev_to_new_ref_picks_up_changes);