aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 22:07:34 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 22:07:34 +0900
commit38706a6f0ceb773d473c4f5ddebf49e8e5ae46dc (patch)
treea38de4fbf27e6834eae3944bbd86ce53b13236cb /crates/mozart/src
parent64f8bb0c1aa16d78c5edc3f3de5dd3ff6e5861de (diff)
downloadphp-mozart-38706a6f0ceb773d473c4f5ddebf49e8e5ae46dc.tar.gz
php-mozart-38706a6f0ceb773d473c4f5ddebf49e8e5ae46dc.tar.zst
php-mozart-38706a6f0ceb773d473c4f5ddebf49e8e5ae46dc.zip
fix(update): apply --minimal-changes via policy preferred versions
The previous implementation pinned every resolved package back to its locked version after the resolve, which discarded the new versions the solver had to pick when a root constraint moved off the lock (e.g. a require bumped from `1.*` to `2.*`). The lock effectively never moved, so transitive cascades from a forced root-level update were lost. Mirror Composer's `Installer::createPolicy(forUpdate=true, minimalUpdate=true)` instead: thread the lock's `name → normalized version` map through the policy as `preferred_versions`. The solver now picks the locked version as a tiebreaker when it still satisfies the active constraints, but moves freely when a constraint forces a different version. Drop the post-process hook entirely.
Diffstat (limited to 'crates/mozart/src')
-rw-r--r--crates/mozart/src/commands/create_project.rs1
-rw-r--r--crates/mozart/src/commands/remove.rs4
-rw-r--r--crates/mozart/src/commands/require.rs3
-rw-r--r--crates/mozart/src/commands/update.rs35
4 files changed, 36 insertions, 7 deletions
diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs
index 89b3e4f..a7964ae 100644
--- a/crates/mozart/src/commands/create_project.rs
+++ b/crates/mozart/src/commands/create_project.rs
@@ -444,6 +444,7 @@ pub async fn execute(
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
console.info("Resolving dependencies...");
diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs
index f41d8b5..c52d410 100644
--- a/crates/mozart/src/commands/remove.rs
+++ b/crates/mozart/src/commands/remove.rs
@@ -278,6 +278,7 @@ pub async fn execute(
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
// Print header messages
@@ -565,6 +566,7 @@ async fn remove_unused(
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
console.info("Resolving dependencies to detect unused packages...");
@@ -922,6 +924,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
let resolved = resolve(&request)
.await
@@ -982,6 +985,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
let resolved2 = resolve(&request2)
.await
diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs
index 0816d13..3ff5ced 100644
--- a/crates/mozart/src/commands/require.rs
+++ b/crates/mozart/src/commands/require.rs
@@ -666,6 +666,7 @@ pub async fn execute(
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
// Print header messages
@@ -1077,6 +1078,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
let resolved = resolver::resolve(&request)
@@ -1155,6 +1157,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: indexmap::IndexMap::new(),
};
let resolved = resolver::resolve(&request)
diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs
index c4ebdf7..bb99e26 100644
--- a/crates/mozart/src/commands/update.rs
+++ b/crates/mozart/src/commands/update.rs
@@ -1119,6 +1119,30 @@ pub async fn run(
.and_then(|v| v.as_bool())
.unwrap_or(false);
+ // For `--minimal-changes` without a per-package update list, feed the
+ // lock's pinned versions into the resolver as preferred-version
+ // overrides. Mirrors Composer's
+ // `Installer::createPolicy(forUpdate=true, minimalUpdate=true)` branch.
+ let preferred_versions: IndexMap<String, String> =
+ if args.minimal_changes && raw_packages.is_empty() && lock_path.exists() {
+ match lockfile::LockFile::read_from_file(&lock_path) {
+ Ok(lock) => {
+ let mut map = IndexMap::new();
+ for pkg in lock
+ .packages
+ .iter()
+ .chain(lock.packages_dev.iter().flatten())
+ {
+ map.insert(pkg.name.to_lowercase(), locked_version_normalized(pkg));
+ }
+ map
+ }
+ Err(_) => IndexMap::new(),
+ }
+ } else {
+ IndexMap::new()
+ };
+
let request = ResolveRequest {
root_name: composer_json.name.clone(),
root_version: composer_json.version.clone(),
@@ -1159,6 +1183,7 @@ pub async fn run(
locked_packages,
block_abandoned,
root_branch_alias: extract_root_branch_alias(&composer_json),
+ preferred_versions,
};
// Step 6: Print header and run resolver
@@ -1294,13 +1319,8 @@ pub async fn run(
resolved = apply_partial_update(resolved, lock, &update_packages);
}
}
- } else if args.minimal_changes && update_packages.is_empty() {
- // Full update with --minimal-changes: pin everything to locked versions
- // (only updates packages whose constraints have changed in composer.json)
- if let Some(ref lock) = old_lock {
- console.info("Minimal changes mode: preserving locked versions where possible.");
- resolved = apply_minimal_changes(resolved, lock);
- }
+ } else if args.minimal_changes && update_packages.is_empty() && old_lock.is_some() {
+ console.info("Minimal changes mode: preserving locked versions where possible.");
}
// Apply --patch-only filter: restrict updates to patch-level changes only
@@ -2298,6 +2318,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: IndexMap::new(),
};
let resolved = resolve(&request).await.expect("Resolution should succeed");