Allow replace to work as originally intended when replace is from a trusted repo #6843
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem
replace
was rendered basically inert by #2690 because one particularrepository composer can use is open to unvetted submissions, and this could allow any malicious publisher to make Composer select their replacement of a popular dependency.
However, if the repository a
replace
originates from is not open, but rather is maintained entirely by an organization the composer user trusts, then it is still safe to honor thosereplace
relationships, and when working with such repositories it would be incredibly useful for these to work like they were originally intended. So, this PR replaces hardcoded logic that was tailored to packagist with a decision based on a repository-specific property.Motivation
I'm working on a project that aims to utilize Composer more like the package manager of a Linux distribution than as a developer tool. That is, it would primarily install things not from packagist, but from a more stable repository of selected packages that are forked from upstream sources and maintained for security and major bugfixes only. (Drupal 8 is the impetus, you can read more about it here.)
If I name my vendors and packages to match those in packagist and list my repository in the root
composer.json
, my packages are correctly preferred due to repository priority and packagist always being lowest. Awesome! But I think there are some real downsides to using the names for vendors and packages found in packagist, such asSo instead of using the same vendor names, I've been experimenting with suffixing "@iglue" to vendors, e.g. symfony@iglue/routing and using
replace
to allow them to satisfy dependencies. But that requires this PR forreplace
to have its originally intended effect.What's changed?
trust-replace
property is added to all repositories. By default it is false, which retains existing solver behavior.trust-replace
can be set by the user in the root composer.json by adding it to a repository's options, or in the case of composer repositories, it can be set by the repository through the repository's root packages.json. Values in composer.json take precedence.Pool::computeWhatProvides()
).Benefits
replace
work again!Considerations
packages.json
because any such repository must be added by the user to a project's composer.json, and this implies the user trusts that repository. Any repository in a project's composer.json already has the means to override which code gets installed by publishing alternative packages under the same vendor/package name as is found on packagist.trust-replace
defaulting to false to prevent surprises with developers publishing to packagist and getting different solver behavior.hasProviders() == true
, the repository .json dumps also need to be modified from what packagist does to include candidate replacement package data within the provider files of the packages they replace. So for example/p/symfony/routing%hash%.json
needs to contain also packages in that repository of different names that replacesymfony/routing
.I think all of of those conditions being met in reality would be rare, but this is an area of possible future work. Currently, the first replacement found among the repository's packages is selected, so it's only somewhat stable.
I'd preliminarily suggest a heuristic of preferring packages that claim to replace fewer things. This would produce the "right" behavior at least in cases like not selecting the whole of
symfony/symfony
as the best replacement for just one of its components. The below test code could then be added tosolverInstallReplacedPackageWithRepoTrustProvider()
; currently it exposes the solver ambiguity and results in test failure.I'd be happy to update the
doc/
folder before any merging happened.