diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 2f52de0f3..51b7c40b6 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -24,6 +24,7 @@ use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Request; use Composer\DependencyResolver\Solver; use Composer\DependencyResolver\SolverProblemsException; +use Composer\DependencyResolver\PolicyInterface; use Composer\Downloader\DownloadManager; use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; @@ -52,6 +53,7 @@ use Composer\Repository\RootPackageRepository; use Composer\Repository\PlatformRepository; use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryManager; +use Composer\Repository\LockArrayRepository; use Composer\Script\ScriptEvents; /** @@ -365,12 +367,10 @@ class Installer // doing a full update } - if ($this->updateAllowList) { - if (!$lockedRepository) { - $this->io->writeError('Cannot update only a partial set of packages without a lock file present.', true, IOInterface::QUIET); + if (($this->updateAllowList || $this->updateMirrors) && !$lockedRepository) { + $this->io->writeError('Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.', true, IOInterface::QUIET); - return 1; - } + return 1; } $this->io->writeError('Loading composer repositories with package information'); @@ -387,22 +387,7 @@ class Installer } $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository); - - // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata - if ($this->updateMirrors && $lockedRepository) { - foreach ($lockedRepository->getPackages() as $lockedPackage) { - // exclude alias packages here as for root aliases, both alias and aliased are - // present in the lock repo and we only want to require the aliased version - if (!$lockedPackage instanceof AliasPackage) { - $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); - } - } - } else { - $links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); - foreach ($links as $link) { - $request->requireName($link->getTarget(), $link->getConstraint()); - } - } + $this->requirePackagesForUpdate($request, $lockedRepository, true); // pass the allow list into the request, so the pool builder can apply it if ($this->updateAllowList) { @@ -442,7 +427,7 @@ class Installer $this->io->writeError('Nothing to modify in lock file'); } - $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy); + $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository); if ($exitCode !== 0) { return $exitCode; } @@ -555,7 +540,7 @@ class Installer * Run the solver a second time on top of the existing update result with only the current result set in the pool * and see what packages would get removed if we only had the non-dev packages in the solver request */ - protected function extractDevPackages(LockTransaction $lockTransaction, $platformRepo, $aliases, $policy) + protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, LockArrayRepository $lockedRepository = null) { if (!$this->package->getDevRequires()) { return 0; @@ -572,11 +557,7 @@ class Installer $repositorySet->addRepository($resultRepo); $request = $this->createRequest($this->fixedRootPackage, $platformRepo); - - $links = $this->package->getRequires(); - foreach ($links as $link) { - $request->requireName($link->getTarget(), $link->getConstraint()); - } + $this->requirePackagesForUpdate($request, $lockedRepository, false); $pool = $repositorySet->createPoolWithAllPackages(); @@ -815,12 +796,9 @@ class Installer } /** - * @param RootPackageInterface $rootPackage - * @param PlatformRepository $platformRepo - * @param RepositoryInterface|null $lockedRepository * @return Request */ - private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, $lockedRepository = null) + private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, LockArrayRepository $lockedRepository = null) { $request = new Request($lockedRepository); @@ -851,6 +829,33 @@ class Installer return $request; } + private function requirePackagesForUpdate(Request $request, LockArrayRepository $lockedRepository = null, $includeDevRequires = true) + { + // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata + if ($this->updateMirrors) { + $excludedPackages = array(); + if (!$includeDevRequires) { + $excludedPackages = array_flip($this->locker->getDevPackageNames()); + } + + foreach ($lockedRepository->getPackages() as $lockedPackage) { + // exclude alias packages here as for root aliases, both alias and aliased are + // present in the lock repo and we only want to require the aliased version + if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) { + $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + } + } + } else { + $links = $this->package->getRequires(); + if ($includeDevRequires) { + $links = array_merge($links, $this->package->getDevRequires()); + } + foreach ($links as $link) { + $request->requireName($link->getTarget(), $link->getConstraint()); + } + } + } + /** * @param bool $forUpdate * @return array @@ -870,7 +875,7 @@ class Installer * @param array $links * @return array */ - private function extractPlatformRequirements($links) + private function extractPlatformRequirements(array $links) { $platformReqs = array(); foreach ($links as $link) { diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test b/tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test index 74007af7b..1738b2a84 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test @@ -31,7 +31,7 @@ Partial update without lock file should error --RUN-- update b/unstable --EXPECT-OUTPUT-- -Cannot update only a partial set of packages without a lock file present. +Cannot update only a partial set of packages without a lock file present. Run `composer update` to generate a lock file. --EXPECT-EXIT-CODE-- 1 --EXPECT-- diff --git a/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test b/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test new file mode 100644 index 000000000..0b47f4b5a --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-mirrors-fails-with-new-req.test @@ -0,0 +1,65 @@ +--TEST-- +Update mirrors with a new root require which is not yet in lock should simply ignore it +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + {"name": "a/a", "version": "1.0.0"}, + {"name": "a/a", "version": "1.1.0"}, + {"name": "b/b", "version": "1.0.0"}, + {"name": "b/b", "version": "1.1.0"}, + {"name": "new/req", "version": "1.0.0"}, + {"name": "new/req", "version": "1.1.0"} + ] + } + ], + "require": { + "a/a": "1.*", + "new/req": "1.*" + }, + "require-dev": { + "b/b": "1.*" + } +} +--INSTALLED-- +[ + {"name": "a/a", "version": "1.0.0"}, + {"name": "b/b", "version": "1.0.0"} +] +--LOCK-- +{ + "packages": [ + {"name": "a/a", "version": "1.0.0", "type": "library"} + ], + "packages-dev": [ + {"name": "b/b", "version": "1.0.0", "type": "library"} + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update mirrors +--EXPECT-LOCK-- +{ + "packages": [ + {"name": "a/a", "version": "1.0.0", "type": "library"} + ], + "packages-dev": [ + {"name": "b/b", "version": "1.0.0", "type": "library"} + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT--