aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-sat-resolver
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 13:17:04 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 13:17:04 +0900
commit87f5bcdc2d0e5fbec3848de3865d6c0d47d623ea (patch)
treec5eca27fa8cafd0fe1d68a38adac7a7cbe6b112c /crates/mozart-sat-resolver
parent3c0527aa63574f17c9f372b6187d5690e0cbaff0 (diff)
downloadphp-mozart-87f5bcdc2d0e5fbec3848de3865d6c0d47d623ea.tar.gz
php-mozart-87f5bcdc2d0e5fbec3848de3865d6c0d47d623ea.tar.zst
php-mozart-87f5bcdc2d0e5fbec3848de3865d6c0d47d623ea.zip
fix(policy): prefer replaced original over replacer in cross-name pick
Mirrors the `replaces()` shortcut in Composer's `DefaultPolicy::compareByPriority` (the cross-package `ignoreReplace=false` pass). When two candidates with different names both satisfy a request — say `update a/installed` finds the real `a/installed` package alongside an `a/replacer` declaring `replace: { "a/installed": "..." }` — the policy now picks the replaced original. Without this, the comparison falls through to the package-id tie-break and silently lands on whichever entry was inserted first (here, the replacer with the wrong source ref). Net effect on installer fixtures: `update_dev_ignores_providers` newly green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-sat-resolver')
-rw-r--r--crates/mozart-sat-resolver/src/policy.rs18
1 files changed, 17 insertions, 1 deletions
diff --git a/crates/mozart-sat-resolver/src/policy.rs b/crates/mozart-sat-resolver/src/policy.rs
index a2678d5..a253c39 100644
--- a/crates/mozart-sat-resolver/src/policy.rs
+++ b/crates/mozart-sat-resolver/src/policy.rs
@@ -86,7 +86,23 @@ impl DefaultPolicy {
};
}
- // Different names: sort by package ID for reproducibility
+ // Different names: when one package replaces the other, prefer the
+ // *replaced* original. Mirrors the `replaces()` shortcut in
+ // Composer's `DefaultPolicy::compareByPriority` (the cross-package
+ // `ignoreReplace=false` pass). Without this, a request like
+ // `update a/installed` where the pool also contains an
+ // `a/replacer` declaring `replace: { "a/installed": "dev-master" }`
+ // could fall through to package-id tie-break and land on the
+ // replacer instead of the package the user actually asked for.
+ if pkg_a.replaces.iter().any(|link| link.target == pkg_b.name) {
+ return std::cmp::Ordering::Greater;
+ }
+ if pkg_b.replaces.iter().any(|link| link.target == pkg_a.name) {
+ return std::cmp::Ordering::Less;
+ }
+
+ // Different names, no replace relationship: sort by package ID
+ // for reproducibility.
pkg_a.id.cmp(&pkg_b.id)
}