aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/installer_executor
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 12:10:38 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 12:10:38 +0900
commitdffb6244ebb432477b83631d68584bbc7186dd94 (patch)
tree4be972cb21de544c53108a244ba4288f4467d544 /crates/mozart-registry/src/installer_executor
parented77ff97d6c137ef58f0464b7a9b08bc2b875bd2 (diff)
downloadphp-mozart-dffb6244ebb432477b83631d68584bbc7186dd94.tar.gz
php-mozart-dffb6244ebb432477b83631d68584bbc7186dd94.tar.zst
php-mozart-dffb6244ebb432477b83631d68584bbc7186dd94.zip
fix(install): treat dev-reference shifts as upgrades
Composer's Transaction fires an UpdateOperation when an installed package's source/dist reference moved, even if the version string is unchanged — that is how a `dev-main#abcd` root require pinning a new commit propagates through `composer install`. Mozart was checking only (name, version) and short-circuiting to Skip, so the package stayed pinned to whatever reference installed.json carried. Compare references in compute_operations and route mismatches into Action::Update. The trace recorder needs the from-side display string to include the reference suffix (`dev-master abc123`) so the EXPECT output matches Composer's UpdateOperation::format; thread that through PackageOperation::Update as a separate from_full_pretty field while keeping from_version (sans suffix) for the upgrade-vs-downgrade direction check, which has to compare normalized versions like Composer's VersionParser::isUpgrade does. Unblocks update_reference, update_reference_picks_latest, and updating_dev_updates_url_and_reference installer fixtures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry/src/installer_executor')
-rw-r--r--crates/mozart-registry/src/installer_executor/mod.rs65
-rw-r--r--crates/mozart-registry/src/installer_executor/trace_recorder.rs3
2 files changed, 62 insertions, 6 deletions
diff --git a/crates/mozart-registry/src/installer_executor/mod.rs b/crates/mozart-registry/src/installer_executor/mod.rs
index ff3d7a8..704031f 100644
--- a/crates/mozart-registry/src/installer_executor/mod.rs
+++ b/crates/mozart-registry/src/installer_executor/mod.rs
@@ -15,6 +15,7 @@
use std::path::PathBuf;
+use crate::installed::InstalledPackageEntry;
use crate::lockfile::{LockAlias, LockedPackage};
pub mod filesystem;
@@ -30,9 +31,13 @@ pub enum PackageOperation<'a> {
/// `package.dist`/`package.source`.
Install { package: &'a LockedPackage },
/// Replace an existing install with a new version. `from_version` is the
- /// pretty version that was installed before.
+ /// pretty version that was installed before (no reference suffix —
+ /// drives the upgrade-vs-downgrade direction). `from_full_pretty` is the
+ /// formatted display string (`dev-master abc123`) used verbatim in the
+ /// trace output.
Update {
from_version: &'a str,
+ from_full_pretty: &'a str,
package: &'a LockedPackage,
},
/// Mark an alias of a real package as installed. No filesystem effects —
@@ -72,15 +77,65 @@ pub fn format_full_pretty_version(pkg: &LockedPackage) -> String {
/// alternate pretty version (used by `MarkAliasInstalled` so the alias's
/// `3.2.x-dev` text is rendered with the *target's* reference).
pub fn format_full_pretty_with_pretty(pretty_version: &str, pkg: &LockedPackage) -> String {
- let is_dev = mozart_semver::Version::parse(&pkg.version)
+ let source_ref = pkg.source.as_ref().and_then(|s| s.reference.as_deref());
+ let dist_ref = pkg.dist.as_ref().and_then(|d| d.reference.as_deref());
+ let source_type = pkg.source.as_ref().map(|s| s.source_type.as_str());
+ format_full_pretty_with_refs(
+ pretty_version,
+ &pkg.version,
+ source_ref,
+ dist_ref,
+ source_type,
+ )
+}
+
+/// Mirror Composer's `BasePackage::getFullPrettyVersion()` for an
+/// `InstalledPackageEntry`. Same display rules as
+/// [`format_full_pretty_version`] but pulls source/dist info out of the
+/// installed.json `source`/`dist` JSON values.
+pub fn format_full_pretty_version_for_installed(entry: &InstalledPackageEntry) -> String {
+ let source_ref = entry
+ .source
+ .as_ref()
+ .and_then(|v| v.get("reference"))
+ .and_then(|v| v.as_str());
+ let dist_ref = entry
+ .dist
+ .as_ref()
+ .and_then(|v| v.get("reference"))
+ .and_then(|v| v.as_str());
+ let source_type = entry
+ .source
+ .as_ref()
+ .and_then(|v| v.get("type"))
+ .and_then(|v| v.as_str());
+ format_full_pretty_with_refs(
+ &entry.version,
+ &entry.version,
+ source_ref,
+ dist_ref,
+ source_type,
+ )
+}
+
+/// Core of `BasePackage::getFullPrettyVersion()` factored over raw
+/// fields so both [`LockedPackage`] and [`InstalledPackageEntry`] can share
+/// the rendering logic. `version` drives the dev-stability check; the result
+/// is `pretty_version` plus a reference suffix when the package is a dev
+/// branch backed by git/hg (with sha1 references truncated to 7 chars).
+fn format_full_pretty_with_refs(
+ pretty_version: &str,
+ version: &str,
+ source_ref: Option<&str>,
+ dist_ref: Option<&str>,
+ source_type: Option<&str>,
+) -> String {
+ let is_dev = mozart_semver::Version::parse(version)
.map(|v| matches!(v.pre_release.as_deref(), Some("dev")) || v.is_dev_branch)
.unwrap_or(false);
if !is_dev {
return pretty_version.to_string();
}
- let source_ref = pkg.source.as_ref().and_then(|s| s.reference.as_deref());
- let dist_ref = pkg.dist.as_ref().and_then(|d| d.reference.as_deref());
- let source_type = pkg.source.as_ref().map(|s| s.source_type.as_str());
// Composer falls back to dist reference only when no source type is set
// (or the package isn't git/hg — in which case the dev display is skipped
// entirely above).
diff --git a/crates/mozart-registry/src/installer_executor/trace_recorder.rs b/crates/mozart-registry/src/installer_executor/trace_recorder.rs
index 44fceea..785d161 100644
--- a/crates/mozart-registry/src/installer_executor/trace_recorder.rs
+++ b/crates/mozart-registry/src/installer_executor/trace_recorder.rs
@@ -69,6 +69,7 @@ impl InstallerExecutor for TraceRecorderExecutor {
}
PackageOperation::Update {
from_version,
+ from_full_pretty,
package,
} => {
let action = if is_upgrade(from_version, &package.version) {
@@ -80,7 +81,7 @@ impl InstallerExecutor for TraceRecorderExecutor {
"{} {} ({} => {})",
action,
package.name,
- from_version,
+ from_full_pretty,
format_full_pretty_version(package)
));
}