aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/commands
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 20:06:49 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 20:06:49 +0900
commitf664a25070b38c5b73995874e0ee15dad23bc5ef (patch)
treed4996dd03dbecc354cabc3226dbcd64e1ba5c6a8 /crates/mozart/src/commands
parentd3cdb9e3f73314e04061d4d18f654e7e80b0dc18 (diff)
downloadphp-mozart-f664a25070b38c5b73995874e0ee15dad23bc5ef.tar.gz
php-mozart-f664a25070b38c5b73995874e0ee15dad23bc5ef.tar.zst
php-mozart-f664a25070b38c5b73995874e0ee15dad23bc5ef.zip
fix(update): union lock and repo requires when expanding --with-deps
Previously requires_for_name returned the lock entry's requires when the package was already locked, falling back to repo requires only when not. That missed the case where the resolver would pick a *newer* version of the locked package that added a new requirement on another locked package — the new dependency stayed pinned and the upgrade was silently suppressed. Union both sources so every candidate version's requires contribute to the unlock cascade. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/commands')
-rw-r--r--crates/mozart/src/commands/update.rs36
1 files changed, 29 insertions, 7 deletions
diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs
index 5e83104..5cf05c4 100644
--- a/crates/mozart/src/commands/update.rs
+++ b/crates/mozart/src/commands/update.rs
@@ -506,20 +506,42 @@ fn is_platform_dep(dep_name: &str) -> bool {
|| dep_name == "php-debug"
}
-/// Look up the require-list for `name`: prefer the lock entry (the version
-/// that will stay if not unlocked) and fall back to the union of repo
-/// requires for not-yet-locked packages. Lowercase names returned.
+/// Look up the require-list for `name`, unioning the lock entry's
+/// requires with every available version's requires from inline /
+/// composer-repo entries. Lowercase names returned, deduped.
+///
+/// Composer's `PoolBuilder::loadPackage` dynamically unlocks any locked
+/// dependency referenced by an allow-listed package's *new* (about-to-be-
+/// resolved) version. Mozart pre-computes the unlock set, so it has to
+/// consider not only the lock-pinned version's requires but also every
+/// candidate version the resolver might pick — otherwise upgrading a
+/// locked package whose new version added a requirement on another
+/// locked package leaves that other package pinned, and the resolver
+/// silently keeps the old version.
fn requires_for_name(
name: &str,
lock_map: &IndexMap<String, &lockfile::LockedPackage>,
repo_requires: &IndexMap<String, IndexSet<String>>,
) -> Option<Vec<String>> {
+ let mut deps: IndexSet<String> = IndexSet::new();
+ let mut seen = false;
if let Some(pkg) = lock_map.get(name) {
- return Some(pkg.require.keys().map(|k| k.to_lowercase()).collect());
+ seen = true;
+ for k in pkg.require.keys() {
+ deps.insert(k.to_lowercase());
+ }
+ }
+ if let Some(set) = repo_requires.get(name) {
+ seen = true;
+ for k in set {
+ deps.insert(k.clone());
+ }
+ }
+ if seen {
+ Some(deps.into_iter().collect())
+ } else {
+ None
}
- repo_requires
- .get(name)
- .map(|set| set.iter().cloned().collect())
}
/// Expand the allow-list with transitive `require` dependencies, stopping at