From 177b894d7d77a5297bee3b2487ef18a0cae7a596 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 3 May 2026 21:50:54 +0900 Subject: fix(resolver): expand root branch-alias and self.version replace links Two related parity gaps surfaced by the `circular-dependency` fixture: 1. The root's `extra.branch-alias` entry was never materialized in the pool, and root-level `replace`/`provide`/`conflict` constraints written as `self.version` were forwarded verbatim. Mirror Composer's `RootAliasPackage`: resolve `self.version` against the root's declared version for the base entry, then add an extra alias entry (carrying the base links plus a duplicate link per `self.version` original retagged at the alias's version) when the root's version matches an `extra.branch-alias` key. 2. `Pool::matches_package` returned on the first link to a target name even when its constraint did not match the query, hiding any later link to the same target. With the alias above, that masked the second `replace` link tagged at the alias version. Keep iterating when target matches but constraint does not, so a later link can still satisfy. --- crates/mozart/src/commands/create_project.rs | 1 + crates/mozart/src/commands/remove.rs | 4 ++++ crates/mozart/src/commands/require.rs | 3 +++ crates/mozart/src/commands/update.rs | 24 ++++++++++++++++++++++++ 4 files changed, 32 insertions(+) (limited to 'crates/mozart/src/commands') diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index fc0efee..89b3e4f 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -443,6 +443,7 @@ pub async fn execute( locked_package_names: indexmap::IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; console.info("Resolving dependencies..."); diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index eb6ee4a..f41d8b5 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -277,6 +277,7 @@ pub async fn execute( locked_package_names: indexmap::IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; // Print header messages @@ -563,6 +564,7 @@ async fn remove_unused( locked_package_names: indexmap::IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; console.info("Resolving dependencies to detect unused packages..."); @@ -919,6 +921,7 @@ mod tests { locked_package_names: IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; let resolved = resolve(&request) .await @@ -978,6 +981,7 @@ mod tests { locked_package_names: IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; let resolved2 = resolve(&request2) .await diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 110bd1a..0816d13 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -665,6 +665,7 @@ pub async fn execute( locked_package_names: indexmap::IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; // Print header messages @@ -1075,6 +1076,7 @@ mod tests { locked_package_names: IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; let resolved = resolver::resolve(&request) @@ -1152,6 +1154,7 @@ mod tests { locked_package_names: IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; let resolved = resolver::resolve(&request) diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 2853fc8..c4ebdf7 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -169,6 +169,28 @@ pub struct UpdateChange { /// /// Recognizes "stable", "RC", "beta", "alpha", "dev" (case-insensitive). /// Defaults to `Stability::Stable` for unrecognized values. +/// Resolve the root composer.json's `extra.branch-alias` against the root's +/// `version` field. Returns the alias target (e.g. `"2.0-dev"`) when both +/// `version` and a matching `branch-alias` entry are present, mirroring +/// Composer's `RootPackageLoader` branch-alias detection on the root package. +/// `None` for projects without a `version` or without a matching alias entry. +fn extract_root_branch_alias( + composer_json: &mozart_core::package::RawPackageData, +) -> Option { + let version = composer_json.version.as_deref()?; + if version.is_empty() { + return None; + } + composer_json + .extra_fields + .get("extra") + .and_then(|extra| extra.get("branch-alias")) + .and_then(|aliases| aliases.as_object()) + .and_then(|map| map.get(version)) + .and_then(|v| v.as_str()) + .map(String::from) +} + fn parse_minimum_stability(s: &str) -> Stability { package::Stability::parse(s) } @@ -1136,6 +1158,7 @@ pub async fn run( locked_package_names, locked_packages, block_abandoned, + root_branch_alias: extract_root_branch_alias(&composer_json), }; // Step 6: Print header and run resolver @@ -2274,6 +2297,7 @@ mod tests { locked_package_names: IndexSet::new(), locked_packages: Vec::new(), block_abandoned: false, + root_branch_alias: None, }; let resolved = resolve(&request).await.expect("Resolution should succeed"); -- cgit v1.3.1