diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-02 22:21:25 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-02 22:21:25 +0900 |
| commit | 8da98493daf5013585e07ec98ca6960a42924edf (patch) | |
| tree | be57603fec29a4bf1e5f546b1ba2e14778595cb3 /crates/mozart-semver/src | |
| parent | 804b5b9a2a7759af24e41408c82dfc60c6092cf3 (diff) | |
| download | php-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')
| -rw-r--r-- | crates/mozart-semver/src/lib.rs | 54 |
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); |
