aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/resolver.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-04 00:49:40 +0900
committernsfisis <nsfisis@gmail.com>2026-05-04 00:49:40 +0900
commit74c188162886755c380a4696d8b684cb28687402 (patch)
tree4a33b20c6ceef51d3720f169918c077c1cb06376 /crates/mozart-registry/src/resolver.rs
parent6449a15de90fe8252fb288bd5eacb99dc2cd699a (diff)
downloadphp-mozart-74c188162886755c380a4696d8b684cb28687402.tar.gz
php-mozart-74c188162886755c380a4696d8b684cb28687402.tar.zst
php-mozart-74c188162886755c380a4696d8b684cb28687402.zip
fix(update): preserve locked refs and aliases on partial update
Partial update of a non-allow-listed dev package now resolves and emits the locked-repo entry verbatim, mirroring Composer's `PoolBuilder`. Three coordinated changes: - resolver: `lock_filter_allows` accepts the locked package's branch- alias normalized versions, not just the base. Without this, root constraints like `~2.1` against a `dev-master` locked package whose branch alias is `2.1.x-dev` failed with "no matching package found". - lockfile: new `lock_pinned_names` field on `LockFileGenerationRequest` routes non-allow-listed packages through `previous_lock_lookup` before `inline_lookup`, so the lock's source/dist references survive even when the inline metadata has moved to a newer commit. - update: `apply_partial_update` skips alias entries — re-pinning their pretty `version` to the base would collapse the alias label and emit a self-referential entry in the new lock's `aliases[]` block. Unblocks partial_update_forces_dev_reference_from_lock_for_non_updated_packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry/src/resolver.rs')
-rw-r--r--crates/mozart-registry/src/resolver.rs30
1 files changed, 25 insertions, 5 deletions
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs
index c592e01..35856c3 100644
--- a/crates/mozart-registry/src/resolver.rs
+++ b/crates/mozart-registry/src/resolver.rs
@@ -800,6 +800,13 @@ pub struct LockedPackageInfo {
pub replaces: Vec<(String, String)>,
pub provides: Vec<(String, String)>,
pub conflicts: Vec<(String, String)>,
+ /// Branch-alias entries to surface alongside the base locked package, as
+ /// `(pretty, normalized)` pairs. Mirrors what
+ /// `Composer\Package\Locker::getLockedRepository` constructs from
+ /// `extra.branch-alias`: a `dev-master` locked package with branch alias
+ /// `2.1.x-dev` needs to expose itself under both versions so root
+ /// constraints like `~2.1` still resolve on a partial update.
+ pub branch_aliases: Vec<(String, String)>,
}
/// A single package in the resolution output.
@@ -1098,15 +1105,28 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R
}
// Build a map first so the filter below knows which (name, version)
- // pairs are the only allowed entries for locked names.
- let locked_name_to_version: IndexMap<String, String> = request
+ // pairs are the only allowed entries for locked names. Each entry holds
+ // the locked normalized version plus any branch-alias normalized
+ // versions Composer's `Locker::getLockedRepository` would expose
+ // alongside the base. Without the alias entries, an inline-package or
+ // VCS source providing the same `dev-master` + alias as the lock would
+ // have its alias filtered out, leaving root constraints like `~2.1` —
+ // which can only match the alias version, not the raw `dev-master` —
+ // unsatisfiable on a partial update.
+ let locked_name_to_versions: IndexMap<String, Vec<String>> = request
.locked_packages
.iter()
- .map(|p| (p.name.to_lowercase(), p.version_normalized.clone()))
+ .map(|p| {
+ let mut versions = vec![p.version_normalized.clone()];
+ for (_, alias_normalized) in &p.branch_aliases {
+ versions.push(alias_normalized.clone());
+ }
+ (p.name.to_lowercase(), versions)
+ })
.collect();
let lock_filter_allows = |name: &str, version: &str| -> bool {
- match locked_name_to_version.get(&name.to_lowercase()) {
- Some(locked_version) => locked_version == version,
+ match locked_name_to_versions.get(&name.to_lowercase()) {
+ Some(locked_versions) => locked_versions.iter().any(|v| v == version),
None => true,
}
};