diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 94d487878..f54fffc75 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -358,9 +358,12 @@ class Installer $repositories = null; // initialize locker to create aliased packages - $installFromLock = false; - if (!$this->update && $this->locker->isLocked()) { - $installFromLock = true; + $installFromLock = !$this->update && $this->locker->isLocked(); + + // initialize locked repo if we are installing from lock or in a partial update + // and a lock file is present as we need to force install non-whitelisted lock file + // packages in that case + if ($installFromLock || (!empty($this->updateWhitelist) && $this->locker->isLocked())) { try { $lockedRepository = $this->locker->getLockedRepository($withDevReqs); } catch (\RuntimeException $e) { @@ -384,18 +387,20 @@ class Installer // creating repository pool $policy = $this->createPolicy(); - $pool = $this->createPool($withDevReqs, $lockedRepository); + $pool = $this->createPool($withDevReqs, $installFromLock ? $lockedRepository : null); $pool->addRepository($installedRepo, $aliases); - if ($installFromLock) { - $pool->addRepository($lockedRepository, $aliases); - } - if (!$installFromLock) { $repositories = $this->repositoryManager->getRepositories(); foreach ($repositories as $repository) { $pool->addRepository($repository, $aliases); } } + // Add the locked repository after the others in case we are doing a + // partial update so missing packages can be found there still. + // For installs from lock it's the only one added so it is first + if ($lockedRepository) { + $pool->addRepository($lockedRepository, $aliases); + } // creating requirements request $request = $this->createRequest($this->package, $platformRepo); @@ -432,16 +437,7 @@ 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->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(); - } + $currentPackages = $this->getCurrentPackages($withDevReqs, $installedRepo); // collect packages to fixate from root requirements as well as installed packages $candidates = array(); @@ -500,7 +496,7 @@ class Installer } // 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, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-links'); // solve dependencies $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request); @@ -516,7 +512,7 @@ class Installer } // force dev packages to be updated if we update or install from a (potentially new) lock - $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, 'force-updates', $operations); + $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, 'force-updates', $operations); // execute operations if (!$operations) { @@ -774,7 +770,7 @@ class Installer return $request; } - private function processDevPackages($localRepo, $pool, $policy, $repositories, $lockedRepository, $installFromLock, $task, array $operations = null) + private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $installFromLock, $withDevReqs, $task, array $operations = null) { if ($task === 'force-updates' && null === $operations) { throw new \InvalidArgumentException('Missing operations argument'); @@ -783,6 +779,10 @@ class Installer $operations = array(); } + if (!$installFromLock && $this->updateWhitelist) { + $currentPackages = $this->getCurrentPackages($withDevReqs, $installedRepo); + } + foreach ($localRepo->getCanonicalPackages() as $package) { // skip non-dev packages if (!$package->isDev()) { @@ -823,6 +823,26 @@ class Installer if ($this->update) { // skip package if the whitelist is enabled and it is not in it if ($this->updateWhitelist && !$this->isUpdateable($package)) { + // check if non-updateable packages are out of date compared to the lock file to ensure we don't corrupt it + foreach ($currentPackages as $curPackage) { + if ($curPackage->isDev() && $curPackage->getName() === $package->getName() && $curPackage->getVersion() === $package->getVersion()) { + if ($task === 'force-links') { + $package->setRequires($curPackage->getRequires()); + $package->setConflicts($curPackage->getConflicts()); + $package->setProvides($curPackage->getProvides()); + $package->setReplaces($curPackage->getReplaces()); + } elseif ($task === 'force-updates') { + if (($curPackage->getSourceReference() && $curPackage->getSourceReference() !== $package->getSourceReference()) + || ($curPackage->getDistReference() && $curPackage->getDistReference() !== $package->getDistReference()) + ) { + $operations[] = new UpdateOperation($package, $curPackage); + } + } + + break; + } + } + continue; } @@ -880,6 +900,23 @@ class Installer return $operations; } + /** + * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback + */ + private function getCurrentPackages($withDevReqs, $installedRepo) + { + if ($this->locker->isLocked()) { + try { + return $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 + return $this->locker->getLockedRepository()->getPackages(); + } + } + + return $installedRepo->getPackages(); + } + private function getRootAliases() { if (!$this->update && $this->locker->isLocked()) { diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test b/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test index 4b2c62a53..4473940bc 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test @@ -65,5 +65,5 @@ update c/uptodate "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) +Updating a/old (0.9.0) to a/old (1.0.0) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test b/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test new file mode 100644 index 000000000..4533d5a94 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/partial-update-forces-dev-reference-from-lock-for-non-updated-packages.test @@ -0,0 +1,97 @@ +--TEST-- +Partial update forces updates dev reference from lock file for non whitelisted packages +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "newmaster-a2", "type": "git", "url": "" } + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "newmaster-b2", "type": "git", "url": "" } + } + ] + } + ], + "require": { + "a/a": "~2.1", + "b/b": "~2.1" + }, + "minimum-stability": "dev" +} +--INSTALLED-- +[ + { + "name": "a/a", "version": "dev-master", "version_normalized": "9999999-dev", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", "version_normalized": "9999999-dev", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-b", "type": "git", "url": "" }, + "type": "library" + } +] +--LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "newmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-b", "type": "git", "url": "" }, + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update b/b +--EXPECT-LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "newmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "newmaster-b2", "type": "git", "url": "" }, + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT-- +Updating a/a (dev-master oldmaster-a) to a/a (dev-master newmaster-a) +Updating b/b (dev-master oldmaster-b) to b/b (dev-master newmaster-b2) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test index 5c3508840..9623b51cf 100644 --- a/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test +++ b/tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test @@ -65,6 +65,6 @@ update b/unstable "platform-dev": [] } --EXPECT-- +Updating b/unstable (1.1.0-alpha) to b/unstable (1.0.0) 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) diff --git a/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test b/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test new file mode 100644 index 000000000..9a6f0ab9e --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/partial-update-installs-from-lock-even-missing.test @@ -0,0 +1,105 @@ +--TEST-- +Partial update installs from lock even if package don't exist in public repo anymore +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } }, + "source": { "reference": "newmaster-a2", "type": "git", "url": "" } + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } }, + "source": { "reference": "newmaster-b2", "type": "git", "url": "" }, + "require": { "a/a": "dev-master" } + } + ] + } + ], + "require": { + "a/a": "~2.1", + "b/b": "~2.1" + }, + "minimum-stability": "dev" +} +--INSTALLED-- +[ + { + "name": "a/a", "version": "dev-master", "version_normalized": "9999999-dev", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", "version_normalized": "9999999-dev", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-b", "type": "git", "url": "" }, + "require": { "a/a": "dev-master" }, + "type": "library" + } +] +--LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.2.x-dev" } }, + "source": { "reference": "newmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.1.x-dev" } }, + "source": { "reference": "oldmaster-b", "type": "git", "url": "" }, + "require": { "a/a": "dev-master" }, + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--RUN-- +update b/b +--EXPECT-LOCK-- +{ + "packages": [ + { + "name": "a/a", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.2.x-dev" } }, + "source": { "reference": "newmaster-a", "type": "git", "url": "" }, + "type": "library" + }, + { + "name": "b/b", "version": "dev-master", + "extra": { "branch-alias": { "dev-master": "2.3.x-dev" } }, + "source": { "reference": "newmaster-b2", "type": "git", "url": "" }, + "require": { "a/a": "dev-master" }, + "type": "library" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} +--EXPECT-- +Updating a/a (dev-master oldmaster-a) to a/a (dev-master newmaster-a) +Updating b/b (dev-master oldmaster-b) to b/b (dev-master newmaster-b2) +Marking a/a (2.2.x-dev newmaster-a) as installed, alias of a/a (dev-master newmaster-a) +Marking b/b (2.3.x-dev newmaster-b2) as installed, alias of b/b (dev-master newmaster-b2) +Marking b/b (2.1.x-dev oldmaster-b) as uninstalled, alias of b/b (dev-master oldmaster-b) +Marking a/a (2.1.x-dev oldmaster-a) as uninstalled, alias of a/a (dev-master oldmaster-a) diff --git a/tests/Composer/Test/Fixtures/installer/update-alias-lock.test b/tests/Composer/Test/Fixtures/installer/update-alias-lock.test index 920321a20..f4f5e98eb 100644 --- a/tests/Composer/Test/Fixtures/installer/update-alias-lock.test +++ b/tests/Composer/Test/Fixtures/installer/update-alias-lock.test @@ -31,6 +31,7 @@ Update aliased package does not mess up the lock file } --LOCK-- { + "_": "outdated lock file, should not have to be loaded in an update", "packages": [ { "package": "a/a", "version": "dev-master", "source-reference": "1234" }, { "package": "a/a", "version": "dev-master", "alias-pretty-version": "1.0.x-dev", "alias-version": "1.0.9999999.9999999-dev" } diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test index 96036e479..c84f0e65d 100644 --- a/tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test @@ -43,6 +43,6 @@ Limited update takes rules from lock if available, and not from the installed re --RUN-- update toupdate/installed --EXPECT-- -Updating old/installed (0.9.0) to old/installed (1.0.0) Updating toupdate/installed (1.0.0) to toupdate/installed (1.1.0) +Updating old/installed (0.9.0) to old/installed (1.0.0) Installing toupdate/notinstalled (1.0.0)