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--