aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/mozart-registry/src/lockfile.rs1
-rw-r--r--crates/mozart-registry/src/resolver.rs22
-rw-r--r--crates/mozart-sat-resolver/src/policy.rs41
-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
-rw-r--r--crates/mozart/tests/installer.rs2
8 files changed, 99 insertions, 10 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs
index 701a6f7..197335f 100644
--- a/crates/mozart-registry/src/lockfile.rs
+++ b/crates/mozart-registry/src/lockfile.rs
@@ -1680,6 +1680,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: IndexMap::new(),
};
let resolved = resolve(&resolve_request)
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs
index 66e923d..33c3659 100644
--- a/crates/mozart-registry/src/resolver.rs
+++ b/crates/mozart-registry/src/resolver.rs
@@ -733,6 +733,11 @@ pub struct ResolveRequest {
/// alias's version for any link originally written as `self.version`.
/// `None` when the root carries no matching `branch-alias` entry.
pub root_branch_alias: Option<String>,
+ /// `name → normalized version` map fed to the policy's preferred-version
+ /// override. Used by `update --minimal-changes` so the solver only moves
+ /// a package when a constraint actually forces a different version.
+ /// Empty for a normal full update.
+ pub preferred_versions: IndexMap<String, String>,
}
/// Full data for a lock-pinned package, used in partial updates. Carried on
@@ -1337,8 +1342,20 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R
return Err(ResolveError::NoSolution(report));
}
- // Create policy and solve
- let policy = DefaultPolicy::new(request.prefer_stable, request.prefer_lowest);
+ // Create policy and solve. When `preferred_versions` is non-empty (the
+ // `--minimal-changes` flow) feed it through the policy so the locked
+ // version wins over the regular highest/lowest pick whenever a candidate
+ // matches it. Mirrors Composer's
+ // `Installer::createPolicy` minimal-update branch.
+ let policy = if request.preferred_versions.is_empty() {
+ DefaultPolicy::new(request.prefer_stable, request.prefer_lowest)
+ } else {
+ DefaultPolicy::with_preferred(
+ request.prefer_stable,
+ request.prefer_lowest,
+ request.preferred_versions.clone(),
+ )
+ };
let fixed_set: IndexSet<u32> = fixed_ids.into_iter().collect();
let solver = Solver::new(rules, &pool, policy, fixed_set);
@@ -1786,6 +1803,7 @@ mod tests {
locked_packages: Vec::new(),
block_abandoned: false,
root_branch_alias: None,
+ preferred_versions: IndexMap::new(),
};
let result = resolve(&request).await;
diff --git a/crates/mozart-sat-resolver/src/policy.rs b/crates/mozart-sat-resolver/src/policy.rs
index a253c39..f45c4f5 100644
--- a/crates/mozart-sat-resolver/src/policy.rs
+++ b/crates/mozart-sat-resolver/src/policy.rs
@@ -10,6 +10,14 @@ pub struct DefaultPolicy {
pub prefer_stable: bool,
/// Whether to prefer lowest versions.
pub prefer_lowest: bool,
+ /// `name → normalized version` overrides used when more than one
+ /// candidate could satisfy a requirement: a literal pinned at the
+ /// preferred version wins outright over the usual highest/lowest pick.
+ /// Mirrors Composer's `DefaultPolicy::pruneToBestVersion` behavior under
+ /// `--minimal-changes`, where the lock's previously-installed versions
+ /// are passed in so the solver only moves a package when a constraint
+ /// actually forces a different version.
+ pub preferred_versions: Option<IndexMap<String, String>>,
}
impl DefaultPolicy {
@@ -17,6 +25,19 @@ impl DefaultPolicy {
DefaultPolicy {
prefer_stable,
prefer_lowest,
+ preferred_versions: None,
+ }
+ }
+
+ pub fn with_preferred(
+ prefer_stable: bool,
+ prefer_lowest: bool,
+ preferred_versions: IndexMap<String, String>,
+ ) -> Self {
+ DefaultPolicy {
+ prefer_stable,
+ prefer_lowest,
+ preferred_versions: Some(preferred_versions),
}
}
@@ -123,6 +144,26 @@ impl DefaultPolicy {
return vec![];
}
+ // Mirror Composer's `DefaultPolicy::pruneToBestVersion` short-circuit:
+ // when a preferred version is set for this package and one of the
+ // candidates matches it exactly, that wins over the regular
+ // highest/lowest pick. Falls through otherwise (e.g. the locked
+ // version no longer satisfies the constraint and was filtered out
+ // before reaching this method).
+ if let Some(ref preferred) = self.preferred_versions {
+ let name = pool.literal_to_package(literals[0]).name.clone();
+ if let Some(preferred_ver) = preferred.get(&name) {
+ let preferred_lits: Vec<Literal> = literals
+ .iter()
+ .filter(|&&lit| pool.literal_to_package(lit).version == *preferred_ver)
+ .copied()
+ .collect();
+ if !preferred_lits.is_empty() {
+ return preferred_lits;
+ }
+ }
+ }
+
// The first literal is the best after sorting
let best_version = &pool.literal_to_package(literals[0]).version;
literals
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");
diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs
index efd0aa2..e9f2749 100644
--- a/crates/mozart/tests/installer.rs
+++ b/crates/mozart/tests/installer.rs
@@ -245,7 +245,7 @@ installer_fixture!(conflict_with_alias_prevents_update_if_not_required);
installer_fixture!(conflict_with_all_dependencies_option_dont_recommend_to_use_it);
installer_fixture!(deduplicate_solver_problems);
installer_fixture!(disjunctive_multi_constraints);
-installer_fixture!(full_update_minimal_changes, ignore);
+installer_fixture!(full_update_minimal_changes);
installer_fixture!(github_issues_4319);
installer_fixture!(github_issues_4795);
installer_fixture!(github_issues_4795_2);