diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-03 23:02:32 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-03 23:02:32 +0900 |
| commit | 26af378d81da76c50593674fa86ed4911aa0e46f (patch) | |
| tree | 913066dc3a65eb5cac92adf60d59d5b0eb5aa5df /crates/mozart-registry | |
| parent | 2b48ae7bcf857bc35de95968513750c2d6e6de7b (diff) | |
| download | php-mozart-26af378d81da76c50593674fa86ed4911aa0e46f.tar.gz php-mozart-26af378d81da76c50593674fa86ed4911aa0e46f.tar.zst php-mozart-26af378d81da76c50593674fa86ed4911aa0e46f.zip | |
fix(update): pattern-match allow-list specifiers and reuse locked metadata
Three related parity gaps surfaced by the `update-allow-list-patterns`
fixture:
1. `mozart-semver`'s wildcard parser turned `*.*` into `>=0 <1` (a
single-major range) because stripping the trailing `.*` left `*`
in the major slot, which `parse()` quietly read as `0`. Composer
reduces such patterns to a plain `*` (unconstrained) — match
that and short-circuit when the stripped base is `*`.
2. `expand_wildcards` passed any non-wildcard specifier straight
through, so a typo like `notexact/Test` (lock has
`notexact/testpackage`) entered the resolver as a real package
name and failed lookup. Mirror Composer's regex-based
`isUpdateAllowed`/`warnAboutNonMatchingUpdateAllowList`: every
specifier — wildcard or not — is matched against locked names
*and* current root-require names, with `*` expanded to `.*`,
and unmatched specs are warned and dropped instead of forwarded.
3. The lockfile generator's metadata loop hit the empty test repo
set when a partial update kept a non-allow-listed package at its
locked version that the inline repo no longer advertised, and
bailed with "Could not find version". Add a `previous_lock`
fallback that synthesizes a `PackagistVersion` straight off the
`LockedPackage` so the lock entry's own metadata stays
authoritative for packages that aren't moving.
Diffstat (limited to 'crates/mozart-registry')
| -rw-r--r-- | crates/mozart-registry/src/lockfile.rs | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 98c0fdc..94983e3 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -494,6 +494,85 @@ impl LockFileGenerationRequest { .find(|cpkg| cpkg.name == name && cpkg.version.version_normalized == version_normalized) .map(|cpkg| cpkg.version) } + + /// Reuse `previous_lock` as a metadata source when no repository can + /// answer for `(name, version_normalized)`. Mirrors the slice of + /// Composer's `PoolBuilder` flow that re-loads locked-only packages + /// straight off the lock: a partial update keeping a package at its + /// locked version doesn't need to re-fetch its metadata, and the + /// repositories may no longer carry that version (e.g. an inline + /// `type: package` repo only listing the new release). + fn previous_lock_lookup( + &self, + name: &str, + version_normalized: &str, + ) -> Option<PackagistVersion> { + let prev = self.previous_lock.as_ref()?; + prev.packages + .iter() + .chain(prev.packages_dev.iter().flatten()) + .find(|p| { + p.name.eq_ignore_ascii_case(name) + && p.version_normalized + .as_deref() + .map(|v| v == version_normalized) + .unwrap_or_else(|| { + mozart_semver::Version::parse(&p.version) + .map(|v| v.to_string() == version_normalized) + .unwrap_or(false) + }) + }) + .map(locked_package_to_packagist_version) + } +} + +/// Synthesize a `PackagistVersion` from a `LockedPackage`. Used by +/// `previous_lock_lookup` so the metadata loop has a complete view even +/// when the surrounding repositories have moved on from a locked version. +fn locked_package_to_packagist_version(pkg: &LockedPackage) -> PackagistVersion { + PackagistVersion { + version: pkg.version.clone(), + version_normalized: pkg + .version_normalized + .clone() + .unwrap_or_else(|| pkg.version.clone()), + require: pkg.require.clone(), + replace: pkg.replace.clone(), + provide: pkg.provide.clone(), + conflict: pkg.conflict.clone(), + dist: pkg.dist.as_ref().map(|d| PackagistDist { + dist_type: d.dist_type.clone(), + url: d.url.clone(), + reference: d.reference.clone(), + shasum: d.shasum.clone(), + }), + source: pkg.source.as_ref().map(|s| PackagistSource { + source_type: s.source_type.clone(), + url: s.url.clone(), + reference: s.reference.clone(), + }), + require_dev: pkg.require_dev.clone(), + suggest: pkg.suggest.clone(), + package_type: pkg.package_type.clone(), + autoload: pkg.autoload.clone(), + autoload_dev: pkg.autoload_dev.clone(), + license: pkg.license.clone(), + description: pkg.description.clone(), + homepage: pkg.homepage.clone(), + keywords: pkg.keywords.clone(), + authors: pkg.authors.clone(), + support: None, + funding: None, + time: pkg.time.clone(), + extra: pkg.extra_fields.get("extra").cloned(), + notification_url: pkg + .extra_fields + .get("notification-url") + .and_then(|v| v.as_str()) + .map(String::from), + default_branch: false, + abandoned: pkg.extra_fields.get("abandoned").cloned(), + } } /// Convert a `PackagistSource` to a `LockedSource`. @@ -857,6 +936,11 @@ pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow:: continue; } + if let Some(prev) = request.previous_lock_lookup(&pkg.name, &pkg.version_normalized) { + package_metadata.insert(pkg.name.clone(), prev); + continue; + } + let queries = [crate::repository::PackageQuery { name: pkg.name.as_str(), constraint: None, |
