aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-semver/src/lib.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-02 22:21:25 +0900
committernsfisis <nsfisis@gmail.com>2026-05-02 22:21:25 +0900
commit8da98493daf5013585e07ec98ca6960a42924edf (patch)
treebe57603fec29a4bf1e5f546b1ba2e14778595cb3 /crates/mozart-semver/src/lib.rs
parent804b5b9a2a7759af24e41408c82dfc60c6092cf3 (diff)
downloadphp-mozart-8da98493daf5013585e07ec98ca6960a42924edf.tar.gz
php-mozart-8da98493daf5013585e07ec98ca6960a42924edf.tar.zst
php-mozart-8da98493daf5013585e07ec98ca6960a42924edf.zip
feat(resolver): add branch-alias support across the resolution pipeline
Plumb Composer's `extra.branch-alias` mechanism end-to-end so a dev branch (e.g. `dev-foobar`) can be installed alongside its numeric alias (e.g. `3.2.x-dev`) and resolve constraints written against the alias target. Concretely: - `mozart-semver`: stop treating pure-numeric `-dev` as a wildcard branch — `3.2.9999999.9999999-dev` (the form `normalizeBranch` emits) now parses as a classical version with `is_dev_branch=false`, so constraints like `3.2.*` match it. - `mozart-registry/composer_repo`: load `type: composer` repositories from `file://` URLs (legacy embedded `packages.json`). - `mozart-registry/resolver`: emit pool entries in pairs for dev branches with `extra.branch-alias`, link them via `is_alias_of`, and apply `@dev`/`@beta` etc. stability suffix flags from root requires. - `mozart-sat-resolver`: alias rules (`PackageAlias` / `PackageInverseAlias`) so alias and target install together; alias packages skipped from same-name conflict indexing. - `mozart-sat-resolver/policy`: `DefaultPolicy` now honors `prefer_stable` via Composer's stability-tier comparison. - `mozart-registry/lockfile`: split resolved set into real packages vs. alias entries; populate the `aliases[]` block. - `mozart-registry/installer_executor`: new `MarkAliasInstalled` operation; `format_full_pretty_version` mirroring `BasePackage::getFullPrettyVersion` (appends source ref[0..7] for dev/git packages). - Test harness rewrites fixture-relative `file://` URLs to absolute paths. Newly green fixtures: `install_branch_alias_composer_repo`, `alias_solver_problems`, `alias_solver_problems2`, `conflict_with_all_dependencies_option_dont_recommend_to_use_it`, `unbounded_conflict_does_not_match_default_branch_with_branch_alias`, `unbounded_conflict_does_not_match_default_branch_with_numeric_branch`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-semver/src/lib.rs')
-rw-r--r--crates/mozart-semver/src/lib.rs54
1 files changed, 24 insertions, 30 deletions
diff --git a/crates/mozart-semver/src/lib.rs b/crates/mozart-semver/src/lib.rs
index 8b77300..dfb0db2 100644
--- a/crates/mozart-semver/src/lib.rs
+++ b/crates/mozart-semver/src/lib.rs
@@ -175,16 +175,14 @@ impl Version {
});
}
- // Handle *-dev suffix (e.g., "2.1.x-dev" or "2.x-dev")
+ // Handle wildcard branch versions like "2.x-dev" / "2.1.x-dev". A
+ // pure-numeric `-dev` (e.g. `1.0.0-dev` or `3.2.9999999.9999999-dev`,
+ // the form Composer's `normalizeBranch` emits for `3.2.x`) is NOT a
+ // branch — it falls through to classical parsing where `-dev` is just
+ // a regular pre-release stability and `is_dev_branch` stays false.
let s_lower = s.to_lowercase();
- if s_lower.ends_with("-dev") || s_lower.ends_with(".x-dev") {
- let base = if s_lower.ends_with("-dev") {
- &s[..s.len() - 4]
- } else {
- s
- };
- // Replace any trailing .x with nothing, parse numeric parts
- let base = base.trim_end_matches(".x").trim_end_matches("-dev");
+ if s_lower.ends_with(".x-dev") {
+ let base = &s[..s.len() - ".x-dev".len()];
let parts: Vec<&str> = base.split('.').collect();
let major = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0);
let minor = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0);
@@ -1340,11 +1338,14 @@ mod tests {
#[test]
fn test_parse_numeric_dev_suffix() {
- // "2.1-dev" — ends with -dev, treated as *-dev suffix branch
+ // Pure-numeric `-dev` (no `.x`) — a regular dev-stability version,
+ // not a wildcard branch. Mirrors the form Composer's `normalizeBranch`
+ // emits for branch aliases like `3.2.x` → `3.2.9999999.9999999-dev`.
let v = Version::parse("2.1-dev").unwrap();
- assert!(v.is_dev_branch);
+ assert!(!v.is_dev_branch);
assert_eq!(v.major, 2);
assert_eq!(v.minor, 1);
+ assert_eq!(v.pre_release.as_deref(), Some("dev"));
}
#[test]
@@ -1447,15 +1448,13 @@ mod tests {
#[test]
fn test_ordering_dev_branch_lt_dev_prerelease() {
- // "1.0.0-dev" ends with "-dev", so the parser treats it as a *-dev suffix branch
- // (is_dev_branch=true, dev_branch_name=None, major=1, minor=0, patch=9999999).
- // "dev-master" is also is_dev_branch=true with dev_branch_name=Some("master").
- // When both are dev branches, they compare by dev_branch_name:
- // Some("master") vs None → Some > None, so dev-master > 1.0.0-dev (x-dev form).
+ // "dev-master" is a named dev branch (is_dev_branch=true), which sorts
+ // below every non-branch version. "1.0.0-dev" is a regular numeric
+ // version with `dev` stability — same form Composer's `normalizeBranch`
+ // produces for branch aliases like `3.2.x` → `3.2.9999999.9999999-dev`.
let dev_branch = Version::parse("dev-master").unwrap();
let dev_prerelease = Version::parse("1.0.0-dev").unwrap();
- // Both are dev branches; "master" branch name > None → dev-master is Greater
- assert!(dev_branch > dev_prerelease);
+ assert!(dev_branch < dev_prerelease);
}
#[test]
@@ -1535,23 +1534,18 @@ mod tests {
#[test]
fn test_ordering_comprehensive_chain() {
- // Note: "1.0.0-dev" is parsed as a *-dev suffix branch (is_dev_branch=true,
- // dev_branch_name=None) due to the "-dev" suffix rule in Version::parse.
- // "dev-foo" is also a dev branch (is_dev_branch=true, dev_branch_name=Some("foo")).
- // Comparing two dev branches uses dev_branch_name: None < Some("foo"), so
- // the *-dev form (None) < "dev-foo" (Some("foo")).
- // For "1.0.0-alpha1", "1.0.0-beta1", "1.0.0-RC1", "1.0.0": normal numeric ordering.
- let dev_x_dev = Version::parse("1.0.0-dev").unwrap(); // *-dev branch, name=None
- let dev_branch = Version::parse("dev-foo").unwrap(); // named branch, name=Some("foo")
+ // "dev-foo" is a named branch (is_dev_branch=true) — sorts below every
+ // non-branch. "1.0.0-dev" is a regular numeric `dev`-stability version,
+ // which sorts below alpha/beta/rc/stable but above named branches.
+ let dev_branch = Version::parse("dev-foo").unwrap();
+ let dev_prerelease = Version::parse("1.0.0-dev").unwrap();
let alpha = Version::parse("1.0.0-alpha1").unwrap();
let beta = Version::parse("1.0.0-beta1").unwrap();
let rc = Version::parse("1.0.0-RC1").unwrap();
let stable = Version::parse("1.0.0").unwrap();
- // Both dev branches; dev_branch_name None < Some("foo")
- assert!(dev_x_dev < dev_branch);
- // dev_branch (is_dev_branch=true) < alpha (is_dev_branch=false)
- assert!(dev_branch < alpha);
+ assert!(dev_branch < dev_prerelease);
+ assert!(dev_prerelease < alpha);
assert!(alpha < beta);
assert!(beta < rc);
assert!(rc < stable);