From 6ec10b18cfe2e473d71f8d786ae0d6a9877864ab Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 3 May 2026 20:40:56 +0900 Subject: fix(install): align partial-update operation order with Composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three coordinated changes to make `update --with-dependencies` produce the same operation trace Composer emits: - LockFileGenerationRequest gains a previous_lock field. When a resolved package matches an entry in the old lock at the same name + version_normalized, its relationship-shaped fields (require / require-dev / conflict / replace / provide / suggest) are carried over verbatim. Source/dist refs and version-shaped fields still refresh from upstream metadata so dev packages can still pick up new commits. Without this carry-over, partial updates regenerated lock entries from upstream COMPOSER repo definitions, which can declare different requires than the lock — and topological_sort then sees a graph Composer's transaction never built. - Transaction's topological_sort and get_root_packages now expand replace/provide targets when matching `require` links to result packages, mirroring Composer's getProvidersInResult. Previously a package was only treated as required when matched by its own name, so packages reached only via replace/provide were mis-classified as roots and the DFS stack visited deps in the wrong order. - compute_operations iterates installed.json in reverse when emitting removals, mirroring Composer's array_unshift onto operations. Two co-orphaned packages otherwise emit removals in the wrong order vs Composer's trace. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/mozart/src/commands/install.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'crates/mozart/src/commands/install.rs') diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 58a01b4..017c08e 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -206,12 +206,19 @@ pub fn compute_operations<'a>( ops.push((pkg, action)); } - // Compute removals: packages in installed but not in locked + // Compute removals: packages in installed but not in locked. Iterate + // installed.json in reverse, mirroring Composer's + // `Transaction::calculateOperations`, which seeds `removeMap` from + // `presentPackages` in order and then `array_unshift`s each entry onto + // `operations` — flipping the iteration order. Without the flip, two + // co-orphaned packages emit removals in the wrong order vs Composer's + // trace. let locked_names: IndexSet = locked.iter().map(|p| p.name.to_lowercase()).collect(); let removals: Vec = installed .packages .iter() + .rev() .filter(|p| !locked_names.contains(&p.name.to_lowercase())) .map(|p| p.name.clone()) .collect(); -- cgit v1.3.1