Handle stability changes correctly, fixes #877
On update, packages that are less stable than the minimum-stability allows will now be downgraded to their correct versions, even if they were installed as unstable already.pull/1013/head
parent
c9f0e458a2
commit
ee60df708d
|
@ -325,6 +325,20 @@ class Installer
|
||||||
// creating requirements request
|
// creating requirements request
|
||||||
$request = $this->createRequest($pool, $this->package, $platformRepo);
|
$request = $this->createRequest($pool, $this->package, $platformRepo);
|
||||||
|
|
||||||
|
if (!$installFromLock) {
|
||||||
|
// remove unstable packages from the localRepo if they don't match the current stability settings
|
||||||
|
$removedUnstablePackages = array();
|
||||||
|
foreach ($localRepo->getPackages() as $package) {
|
||||||
|
if (
|
||||||
|
!$pool->isPackageAcceptable($package->getName(), $package->getStability())
|
||||||
|
&& $this->installationManager->isPackageInstalled($localRepo, $package)
|
||||||
|
) {
|
||||||
|
$removedUnstablePackages[$package->getName()] = true;
|
||||||
|
$request->remove($package->getName(), new VersionConstraint('=', $package->getVersion()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->update) {
|
if ($this->update) {
|
||||||
$this->io->write('<info>Updating dependencies'.($withDevReqs?' (including require-dev)':'').'</info>');
|
$this->io->write('<info>Updating dependencies'.($withDevReqs?' (including require-dev)':'').'</info>');
|
||||||
|
|
||||||
|
@ -339,6 +353,43 @@ class Installer
|
||||||
foreach ($links as $link) {
|
foreach ($links as $link) {
|
||||||
$request->install($link->getTarget(), $link->getConstraint());
|
$request->install($link->getTarget(), $link->getConstraint());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the updateWhitelist is enabled, packages not in it are also fixed
|
||||||
|
// to the version specified in the lock, or their currently installed version
|
||||||
|
if ($this->updateWhitelist) {
|
||||||
|
if ($this->locker->isLocked()) {
|
||||||
|
try {
|
||||||
|
$currentPackages = $this->locker->getLockedRepository($withDevReqs)->getPackages();
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
// fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
|
||||||
|
$currentPackages = $this->locker->getLockedRepository()->getPackages();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$currentPackages = $installedRepo->getPackages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect packages to fixate from root requirements as well as installed packages
|
||||||
|
$candidates = array();
|
||||||
|
foreach ($links as $link) {
|
||||||
|
$candidates[$link->getTarget()] = true;
|
||||||
|
}
|
||||||
|
foreach ($localRepo->getPackages() as $package) {
|
||||||
|
$candidates[$package->getName()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix them to the version in lock (or currently installed) if they are not updateable
|
||||||
|
foreach ($candidates as $candidate => $dummy) {
|
||||||
|
foreach ($currentPackages as $curPackage) {
|
||||||
|
if ($curPackage->getName() === $candidate) {
|
||||||
|
if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) {
|
||||||
|
$constraint = new VersionConstraint('=', $curPackage->getVersion());
|
||||||
|
$request->install($curPackage->getName(), $constraint);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif ($installFromLock) {
|
} elseif ($installFromLock) {
|
||||||
$this->io->write('<info>Installing dependencies'.($withDevReqs?' (including require-dev)':'').' from lock file</info>');
|
$this->io->write('<info>Installing dependencies'.($withDevReqs?' (including require-dev)':'').' from lock file</info>');
|
||||||
|
|
||||||
|
@ -377,44 +428,6 @@ class Installer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the updateWhitelist is enabled, packages not in it are also fixed
|
|
||||||
// to the version specified in the lock, or their currently installed version
|
|
||||||
if ($this->update && $this->updateWhitelist) {
|
|
||||||
if ($this->locker->isLocked()) {
|
|
||||||
try {
|
|
||||||
$currentPackages = $this->locker->getLockedRepository($withDevReqs)->getPackages();
|
|
||||||
} catch (\RuntimeException $e) {
|
|
||||||
// fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
|
|
||||||
$currentPackages = $this->locker->getLockedRepository()->getPackages();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$currentPackages = $installedRepo->getPackages();
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect links from composer as well as installed packages
|
|
||||||
$candidates = array();
|
|
||||||
foreach ($links as $link) {
|
|
||||||
$candidates[$link->getTarget()] = true;
|
|
||||||
}
|
|
||||||
foreach ($localRepo->getPackages() as $package) {
|
|
||||||
$candidates[$package->getName()] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fix them to the version in lock (or currently installed) if they are not updateable
|
|
||||||
foreach ($candidates as $candidate => $dummy) {
|
|
||||||
foreach ($currentPackages as $curPackage) {
|
|
||||||
if ($curPackage->getName() === $candidate) {
|
|
||||||
if ($this->isUpdateable($curPackage)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$constraint = new VersionConstraint('=', $curPackage->getVersion());
|
|
||||||
$request->install($curPackage->getName(), $constraint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// force dev packages to have the latest links if we update or install from a (potentially new) lock
|
// force dev packages to have the latest links if we update or install from a (potentially new) lock
|
||||||
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
|
$this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-links');
|
||||||
|
|
||||||
|
@ -512,9 +525,7 @@ class Installer
|
||||||
$constraint->setPrettyString($rootPackage->getPrettyVersion());
|
$constraint->setPrettyString($rootPackage->getPrettyVersion());
|
||||||
$request->install($rootPackage->getName(), $constraint);
|
$request->install($rootPackage->getName(), $constraint);
|
||||||
|
|
||||||
// fix the version of all installed packages (+ platform) that are not
|
// fix the version of all platform packages to prevent the solver trying to remove those
|
||||||
// in the current local repo to prevent rogue updates (e.g. non-dev
|
|
||||||
// updating when in dev)
|
|
||||||
foreach ($platformRepo->getPackages() as $package) {
|
foreach ($platformRepo->getPackages() as $package) {
|
||||||
$constraint = new VersionConstraint('=', $package->getVersion());
|
$constraint = new VersionConstraint('=', $package->getVersion());
|
||||||
$constraint->setPrettyString($package->getPrettyVersion());
|
$constraint->setPrettyString($package->getPrettyVersion());
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
--TEST--
|
||||||
|
Partial update from lock file should apply lock file and downgrade unstable packages even if not whitelisted
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "a/old", "version": "2.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/old": "*",
|
||||||
|
"b/unstable": "*",
|
||||||
|
"c/uptodate": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--LOCK--
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {
|
||||||
|
"b/unstable": 15
|
||||||
|
},
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
|
--INSTALLED--
|
||||||
|
[
|
||||||
|
{ "name": "a/old", "version": "0.9.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "2.0.0" }
|
||||||
|
]
|
||||||
|
--RUN--
|
||||||
|
update c/uptodate
|
||||||
|
--EXPECT-LOCK--
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "c/uptodate", "version": "2.0.0", "type": "library" }
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
|
--EXPECT--
|
||||||
|
Updating a/old (0.9.0) to a/old (1.0.0)
|
||||||
|
Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)
|
|
@ -0,0 +1,66 @@
|
||||||
|
--TEST--
|
||||||
|
Partial update from lock file should update everything to the state of the lock, remove overly unstable packages
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "a/old", "version": "2.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/old": "*",
|
||||||
|
"b/unstable": "*",
|
||||||
|
"c/uptodate": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--LOCK--
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {
|
||||||
|
"b/unstable": 15
|
||||||
|
},
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
|
--INSTALLED--
|
||||||
|
[
|
||||||
|
{ "name": "a/old", "version": "0.9.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "2.0.0" }
|
||||||
|
]
|
||||||
|
--RUN--
|
||||||
|
update b/unstable
|
||||||
|
--EXPECT-LOCK--
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0", "type": "library" }
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
|
--EXPECT--
|
||||||
|
Updating a/old (0.9.0) to a/old (1.0.0)
|
||||||
|
Updating c/uptodate (2.0.0) to c/uptodate (1.0.0)
|
||||||
|
Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)
|
|
@ -0,0 +1,49 @@
|
||||||
|
--TEST--
|
||||||
|
Partial update without lock file should update everything whitelisted, remove overly unstable packages
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "a/old", "version": "2.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/old": "*",
|
||||||
|
"b/unstable": "*",
|
||||||
|
"c/uptodate": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--INSTALLED--
|
||||||
|
[
|
||||||
|
{ "name": "a/old", "version": "1.0.0" },
|
||||||
|
{ "name": "b/unstable", "version": "1.1.0-alpha" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0" }
|
||||||
|
]
|
||||||
|
--RUN--
|
||||||
|
update b/unstable
|
||||||
|
--EXPECT-LOCK--
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{ "name": "a/old", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "b/unstable", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "c/uptodate", "version": "1.0.0", "type": "library" },
|
||||||
|
{ "name": "d/removed", "version": "1.0.0", "type": "library" }
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
|
--EXPECT--
|
||||||
|
Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0)
|
|
@ -0,0 +1,49 @@
|
||||||
|
--TEST--
|
||||||
|
Downgrading from unstable to more stable package should work even if already installed
|
||||||
|
--COMPOSER--
|
||||||
|
{
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": [
|
||||||
|
{
|
||||||
|
"name": "a/a", "version": "dev-master",
|
||||||
|
"source": { "reference": "abcd", "url": "", "type": "git" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "a/a", "version": "1.0.0",
|
||||||
|
"source": { "reference": "1.0.0", "url": "", "type": "git" },
|
||||||
|
"dist": { "reference": "1.0.0", "url": "", "type": "zip", "shasum": "" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b", "version": "dev-master",
|
||||||
|
"source": { "reference": "abcd", "url": "", "type": "git" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b", "version": "1.0.0",
|
||||||
|
"source": { "reference": "1.0.0", "url": "", "type": "git" },
|
||||||
|
"dist": { "reference": "1.0.0", "url": "", "type": "zip", "shasum": "" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"a/a": "*",
|
||||||
|
"b/b": "*@dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
--INSTALLED--
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "a/a", "version": "dev-master",
|
||||||
|
"source": { "reference": "abcd", "url": "", "type": "git" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b/b", "version": "dev-master",
|
||||||
|
"source": { "reference": "abcd", "url": "", "type": "git" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
--RUN--
|
||||||
|
update
|
||||||
|
--EXPECT--
|
||||||
|
Updating a/a (dev-master abcd) to a/a (1.0.0)
|
|
@ -33,6 +33,11 @@ class InstallationManagerMock extends InstallationManager
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
|
||||||
|
{
|
||||||
|
return $repo->hasPackage($package);
|
||||||
|
}
|
||||||
|
|
||||||
public function install(RepositoryInterface $repo, InstallOperation $operation)
|
public function install(RepositoryInterface $repo, InstallOperation $operation)
|
||||||
{
|
{
|
||||||
$this->installed[] = $operation->getPackage();
|
$this->installed[] = $operation->getPackage();
|
||||||
|
|
Loading…
Reference in New Issue