1
0
Fork 0

Partial updates should remove all unused dependencies

Instead of marking locked packages as fixed, we change the pool builder
to load only the locked version and treat it like a fixed package, but
removing the actual request fix, makes the solver treat it as a regular
optional dependency. As a consequence locked packages may be removed on
a partial update of another package, but they cannot be updated.
pull/9223/head
Nils Adermann 2020-09-17 12:08:04 +02:00
parent d8490ee38f
commit 73e24ea9fb
4 changed files with 94 additions and 5 deletions

View File

@ -62,6 +62,7 @@ class LockTransaction extends Transaction
if ($literal > 0) {
$package = $pool->literalToPackage($literal);
$this->resultPackages['all'][] = $package;
if (!isset($this->unlockableMap[$package->id])) {
$this->resultPackages['non-dev'][] = $package;

View File

@ -123,13 +123,16 @@ class PoolBuilder
public function buildPool(array $repositories, Request $request)
{
$singleVersionPackages = $request->getFixedPackages();
if ($request->getUpdateAllowList()) {
$this->updateAllowList = $request->getUpdateAllowList();
$this->warnAboutNonMatchingUpdateAllowList($request);
foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
if (!$this->isUpdateAllowed($lockedPackage)) {
$request->fixPackage($lockedPackage);
//$request->fixPackage($lockedPackage);
$singleVersionPackages[] = $lockedPackage;
$lockedName = $lockedPackage->getName();
// remember which packages we skipped loading remote content for in this partial update
$this->skippedLoad[$lockedName] = $lockedName;
@ -140,7 +143,7 @@ class PoolBuilder
}
}
foreach ($request->getFixedPackages() as $package) {
foreach ($singleVersionPackages as $package) {
// using MatchAllConstraint here because fixed packages do not need to retrigger
// loading any packages
$this->loadedPackages[$package->getName()] = new MatchAllConstraint();
@ -345,7 +348,7 @@ class PoolBuilder
// apply to
if (isset($this->rootReferences[$name])) {
// do not modify the references on already locked packages
if (!$request->isFixedPackage($package)) {
if ($request->getLockedRepository() !== $package->getRepository() && !$request->isFixedPackage($package)) {
$package->setSourceDistReferences($this->rootReferences[$name]);
}
}
@ -473,7 +476,7 @@ class PoolBuilder
foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) {
if (false !== $index = array_search($lockedPackage, $this->packages, true)) {
$request->unfixPackage($lockedPackage);
//$request->unfixPackage($lockedPackage);
$this->removeLoadedPackage($request, $lockedPackage, $index);
}
}
@ -496,7 +499,7 @@ class PoolBuilder
unset($this->packages[$index]);
if (isset($this->aliasMap[spl_object_hash($package)])) {
foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) {
$request->unfixPackage($aliasPackage);
//$request->unfixPackage($aliasPackage);
unset($this->packages[$aliasIndex]);
}
unset($this->aliasMap[spl_object_hash($package)]);

View File

@ -0,0 +1,46 @@
--TEST--
Removing a package deletes unused dependencies and does not update other dependencies
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "root/a", "version": "1.0.0", "require": { "dep/c": "*", "dep/d": "*"} },
{ "name": "remove/b", "version": "1.0.0", "require": { "dep/c": "*", "dep/e": "*"} },
{ "name": "dep/c", "version": "1.0.0" },
{ "name": "dep/c", "version": "1.2.0" },
{ "name": "dep/d", "version": "1.0.0" },
{ "name": "dep/d", "version": "1.2.0" },
{ "name": "dep/e", "version": "1.0.0" },
{ "name": "unrelated/f", "version": "1.0.0" }
]
}
],
"require": {
"root/a": "*"
}
}
--LOCK--
{
"packages": [
{ "name": "root/a", "version": "1.0.0", "require": { "dep/c": "*", "dep/d": "*"} },
{ "name": "remove/b", "version": "1.0.0", "require": { "dep/c": "*", "dep/e": "*"} },
{ "name": "dep/c", "version": "1.0.0" },
{ "name": "dep/d", "version": "1.0.0" },
{ "name": "dep/e", "version": "1.0.0" },
{ "name": "unrelated/f", "version": "1.0.0" }
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false
}
--RUN--
update remove/b
--EXPECT--
Installing dep/d (1.0.0)
Installing dep/c (1.0.0)
Installing root/a (1.0.0)

View File

@ -0,0 +1,39 @@
--TEST--
Removing a package has no effect if another package would require an update in order to find a correct set of dependencies without the removed package
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "root/a", "version": "1.0.0", "require": { "remove/b": "*", "dep/c": "*"} },
{ "name": "remove/b", "version": "1.0.0", "require": { "dep/c": "*"} },
{ "name": "dep/c", "version": "1.0.0" },
{ "name": "dep/c", "version": "1.2.0", "replace": { "remove/b": "1.0.0"} }
]
}
],
"require": {
"root/a": "*"
}
}
--LOCK--
{
"packages": [
{ "name": "root/a", "version": "1.0.0", "require": { "remove/b": "*", "dep/c": "*"} },
{ "name": "remove/b", "version": "1.0.0", "require": { "dep/c": "*"} },
{ "name": "dep/c", "version": "1.0.0" }
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false
}
--RUN--
update remove/b
--EXPECT--
Installing dep/c (1.0.0)
Installing remove/b (1.0.0)
Installing root/a (1.0.0)