aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/mozart-registry/src/lockfile.rs27
-rw-r--r--crates/mozart-registry/src/resolver.rs30
-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.rs5
7 files changed, 96 insertions, 9 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs
index 94983e3..5a293fe 100644
--- a/crates/mozart-registry/src/lockfile.rs
+++ b/crates/mozart-registry/src/lockfile.rs
@@ -467,6 +467,16 @@ pub struct LockFileGenerationRequest {
/// during partial updates: lock entries are stable across updates that
/// don't touch the package, even if the upstream metadata has drifted.
pub previous_lock: Option<LockFile>,
+ /// Lowercase package names that were held back to their locked version
+ /// on a partial update — i.e. they were NOT in the CLI's allow list and
+ /// were re-pinned by `apply_partial_update`. For these names the lock
+ /// entry's metadata (source/dist references in particular) is canonical:
+ /// inline / composer-repo metadata may have drifted to a newer commit
+ /// that the partial update is explicitly choosing not to take. Mirrors
+ /// Composer's `PoolBuilder`, which keeps non-allow-listed packages at
+ /// the locked-repo entry rather than re-loading them from the inline /
+ /// VCS sources.
+ pub lock_pinned_names: indexmap::IndexSet<String>,
}
impl LockFileGenerationRequest {
@@ -926,6 +936,21 @@ pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow::
let mut package_metadata: IndexMap<String, PackagistVersion> = IndexMap::new();
let repo_set = &request.repositories;
for pkg in &real_resolved {
+ // For packages held back to the locked version on a partial update,
+ // the lock entry is the canonical metadata source. Inline / composer-
+ // repo / VCS sources may have moved to a newer commit that this
+ // partial update is explicitly choosing NOT to take, so consulting
+ // them first would silently bump the source/dist reference. Mirrors
+ // Composer's `PoolBuilder` behaviour: non-allow-listed packages keep
+ // the locked-repo entry rather than re-loading from upstream.
+ let pinned = request.lock_pinned_names.contains(&pkg.name.to_lowercase());
+ if pinned
+ && let Some(prev) = request.previous_lock_lookup(&pkg.name, &pkg.version_normalized)
+ {
+ package_metadata.insert(pkg.name.clone(), prev);
+ continue;
+ }
+
if let Some(inline) = request.inline_lookup(&pkg.name, &pkg.version_normalized) {
package_metadata.insert(pkg.name.clone(), inline);
continue;
@@ -1640,6 +1665,7 @@ mod tests {
crate::cache::Cache::new(std::env::temp_dir().join("mozart-test-cache"), false),
)),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
};
let lock = generate_lock_file(&request).await.unwrap();
@@ -1787,6 +1813,7 @@ mod tests {
false,
))),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
};
let lock = generate_lock_file(&gen_request)
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs
index c592e01..35856c3 100644
--- a/crates/mozart-registry/src/resolver.rs
+++ b/crates/mozart-registry/src/resolver.rs
@@ -800,6 +800,13 @@ pub struct LockedPackageInfo {
pub replaces: Vec<(String, String)>,
pub provides: Vec<(String, String)>,
pub conflicts: Vec<(String, String)>,
+ /// Branch-alias entries to surface alongside the base locked package, as
+ /// `(pretty, normalized)` pairs. Mirrors what
+ /// `Composer\Package\Locker::getLockedRepository` constructs from
+ /// `extra.branch-alias`: a `dev-master` locked package with branch alias
+ /// `2.1.x-dev` needs to expose itself under both versions so root
+ /// constraints like `~2.1` still resolve on a partial update.
+ pub branch_aliases: Vec<(String, String)>,
}
/// A single package in the resolution output.
@@ -1098,15 +1105,28 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R
}
// Build a map first so the filter below knows which (name, version)
- // pairs are the only allowed entries for locked names.
- let locked_name_to_version: IndexMap<String, String> = request
+ // pairs are the only allowed entries for locked names. Each entry holds
+ // the locked normalized version plus any branch-alias normalized
+ // versions Composer's `Locker::getLockedRepository` would expose
+ // alongside the base. Without the alias entries, an inline-package or
+ // VCS source providing the same `dev-master` + alias as the lock would
+ // have its alias filtered out, leaving root constraints like `~2.1` —
+ // which can only match the alias version, not the raw `dev-master` —
+ // unsatisfiable on a partial update.
+ let locked_name_to_versions: IndexMap<String, Vec<String>> = request
.locked_packages
.iter()
- .map(|p| (p.name.to_lowercase(), p.version_normalized.clone()))
+ .map(|p| {
+ let mut versions = vec![p.version_normalized.clone()];
+ for (_, alias_normalized) in &p.branch_aliases {
+ versions.push(alias_normalized.clone());
+ }
+ (p.name.to_lowercase(), versions)
+ })
.collect();
let lock_filter_allows = |name: &str, version: &str| -> bool {
- match locked_name_to_version.get(&name.to_lowercase()) {
- Some(locked_version) => locked_version == version,
+ match locked_name_to_versions.get(&name.to_lowercase()) {
+ Some(locked_versions) => locked_versions.iter().any(|v| v == version),
None => true,
}
};
diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs
index 61cb886..6674ccc 100644
--- a/crates/mozart/src/commands/create_project.rs
+++ b/crates/mozart/src/commands/create_project.rs
@@ -468,6 +468,7 @@ pub async fn execute(
mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()),
),
previous_lock: None,
+ lock_pinned_names: indexmap::IndexSet::new(),
})
.await?;
diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs
index d4b3aef..6498e01 100644
--- a/crates/mozart/src/commands/remove.rs
+++ b/crates/mozart/src/commands/remove.rs
@@ -380,6 +380,7 @@ pub async fn execute(
mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()),
),
previous_lock: old_lock.clone(),
+ lock_pinned_names: indexmap::IndexSet::new(),
})
.await?;
@@ -627,6 +628,7 @@ async fn remove_unused(
mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()),
),
previous_lock: Some(old_lock.clone()),
+ lock_pinned_names: indexmap::IndexSet::new(),
})
.await?;
@@ -946,6 +948,7 @@ mod tests {
),
),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
})
.await
.expect("initial lock file generation should succeed");
@@ -1010,6 +1013,7 @@ mod tests {
),
),
previous_lock: Some(initial_lock.clone()),
+ lock_pinned_names: IndexSet::new(),
})
.await
.expect("post-remove lock file generation should succeed");
diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs
index b302ed9..02e5d8e 100644
--- a/crates/mozart/src/commands/require.rs
+++ b/crates/mozart/src/commands/require.rs
@@ -769,6 +769,7 @@ pub async fn execute(
mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()),
),
previous_lock: old_lock.clone(),
+ lock_pinned_names: indexmap::IndexSet::new(),
})
.await?;
@@ -1103,6 +1104,7 @@ mod tests {
),
),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
})
.await
.expect("Lock file generation should succeed");
@@ -1180,6 +1182,7 @@ mod tests {
),
),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
})
.await
.expect("Lock file generation should succeed");
diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs
index 5210a34..2a0aa88 100644
--- a/crates/mozart/src/commands/update.rs
+++ b/crates/mozart/src/commands/update.rs
@@ -410,6 +410,15 @@ pub fn apply_partial_update(
.into_iter()
.map(|mut pkg| {
let name_lower = pkg.name.to_lowercase();
+ // Alias entries already carry their post-swap shape: the resolver
+ // picked them from the locked-repo branch-alias surface, which is
+ // exactly where the previous lock would have put them. Re-pinning
+ // their `version` to the base's locked pretty would collapse the
+ // alias label into the base, leaving a self-referential entry in
+ // the new lock's `aliases[]` block.
+ if pkg.alias_of_normalized.is_some() {
+ return pkg;
+ }
// If this package is NOT in the update set and we have an old locked version,
// swap it back to the old version to prevent unintended changes.
//
@@ -1230,6 +1239,10 @@ pub async fn run(
continue;
}
names.insert(name_lower.clone());
+ let branch_aliases = lockfile::locked_package_branch_aliases(p)
+ .into_iter()
+ .map(|a| (a.alias, a.alias_normalized))
+ .collect();
infos.push(LockedPackageInfo {
name: name_lower,
pretty_version: p.version.clone(),
@@ -1254,6 +1267,7 @@ pub async fn run(
.iter()
.map(|(k, v)| (k.to_lowercase(), v.clone()))
.collect(),
+ branch_aliases,
});
}
(names, infos)
@@ -1599,6 +1613,25 @@ pub async fn run(
// Step 9: Generate new lock file. `include_dev: true` matches Composer:
// `update --no-dev` still writes a complete lock file with packages-dev
// populated, so a later `install` (with dev_mode) sees them.
+ //
+ // For partial updates, names NOT in the CLI allow list keep their
+ // locked-repo metadata (source/dist references in particular). Computed
+ // here from the same `update_packages` list `apply_partial_update` used
+ // to swap the resolved versions back. Empty for full updates.
+ let lock_pinned_names: IndexSet<String> = if update_packages.is_empty() {
+ IndexSet::new()
+ } else if let Some(lock) = &old_lock {
+ let update_set: IndexSet<String> =
+ update_packages.iter().map(|s| s.to_lowercase()).collect();
+ lock.packages
+ .iter()
+ .chain(lock.packages_dev.iter().flatten())
+ .map(|p| p.name.to_lowercase())
+ .filter(|n| !update_set.contains(n))
+ .collect()
+ } else {
+ IndexSet::new()
+ };
let mut new_lock = lockfile::generate_lock_file(&lockfile::LockFileGenerationRequest {
resolved_packages: resolved,
composer_json_content: composer_json_content.clone(),
@@ -1606,6 +1639,7 @@ pub async fn run(
include_dev: true,
repositories: repositories.clone(),
previous_lock: old_lock.clone(),
+ lock_pinned_names,
})
.await?;
@@ -2504,6 +2538,7 @@ mod tests {
),
),
previous_lock: None,
+ lock_pinned_names: IndexSet::new(),
})
.await
.expect("Lock file generation should succeed");
diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs
index 177decf..955a1ad 100644
--- a/crates/mozart/tests/installer.rs
+++ b/crates/mozart/tests/installer.rs
@@ -311,10 +311,7 @@ installer_fixture!(outdated_lock_file_fails_install);
installer_fixture!(outdated_lock_file_with_new_platform_reqs_fails);
installer_fixture!(partial_update_always_updates_symlinked_path_repos);
installer_fixture!(partial_update_downgrades_non_allow_listed_unstable);
-installer_fixture!(
- partial_update_forces_dev_reference_from_lock_for_non_updated_packages,
- ignore
-);
+installer_fixture!(partial_update_forces_dev_reference_from_lock_for_non_updated_packages);
installer_fixture!(partial_update_from_lock);
installer_fixture!(partial_update_from_lock_with_root_alias);
installer_fixture!(partial_update_installs_from_lock_even_missing, ignore);