From 6925005ac9c115d8105289a3bc9526bc765d2b21 Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 7 Nov 2019 17:35:44 +0100 Subject: [PATCH] Implement update mirrors/nothing/lock as its own installer mode These special commands no longer (ab)use the partial update mechanism but rather create a special install request for all current lock file contents and later override any modified code references to the originals. This leads to up to date remote metadata but no other changes. --- src/Composer/Command/UpdateCommand.php | 16 +++++- .../DependencyResolver/LockTransaction.php | 10 +++- .../DependencyResolver/PoolBuilder.php | 16 +----- src/Composer/Installer.php | 53 +++++++++++-------- src/Composer/Package/AliasPackage.php | 5 ++ src/Composer/Package/Package.php | 17 ++++++ src/Composer/Package/PackageInterface.php | 9 ++++ .../installer/update-mirrors-changes-url.test | 23 +++----- .../update-picks-up-change-of-vcs-type.test | 2 +- tests/Composer/Test/InstallerTest.php | 10 +++- 10 files changed, 104 insertions(+), 57 deletions(-) diff --git a/src/Composer/Command/UpdateCommand.php b/src/Composer/Command/UpdateCommand.php index 99bd2d74b..0c3d3e6c7 100644 --- a/src/Composer/Command/UpdateCommand.php +++ b/src/Composer/Command/UpdateCommand.php @@ -121,6 +121,19 @@ EOT } } + // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead + // they are further mutually exclusive with listing actual package names + $filteredPackages = array_filter($packages, function ($package) { + return !in_array($package, array('lock', 'nothing', 'mirrors'), true); + }); + $updateMirrors = $input->getOption('lock') || count($filteredPackages) != count($packages); + $packages = $filteredPackages; + + if ($updateMirrors && !empty($packages)) { + $io->writeError('You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.'); + return -1; + } + $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); @@ -146,7 +159,8 @@ EOT ->setClassMapAuthoritative($authoritative) ->setApcuAutoloader($apcu) ->setUpdate(true) - ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages) + ->setUpdateMirrors($updateMirrors) + ->setUpdateWhitelist($packages) ->setWhitelistTransitiveDependencies($input->getOption('with-dependencies')) ->setWhitelistAllDependencies($input->getOption('with-all-dependencies')) ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs')) diff --git a/src/Composer/DependencyResolver/LockTransaction.php b/src/Composer/DependencyResolver/LockTransaction.php index 75b2efb5b..7255de3dd 100644 --- a/src/Composer/DependencyResolver/LockTransaction.php +++ b/src/Composer/DependencyResolver/LockTransaction.php @@ -152,11 +152,19 @@ class LockTransaction } // TODO additionalFixedRepository needs to be looked at here as well? - public function getNewLockPackages($devMode) + public function getNewLockPackages($devMode, $updateMirrors = false) { $packages = array(); foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) { if (!($package instanceof AliasPackage) && !($package instanceof RootAliasPackage)) { + // if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is + if ($updateMirrors && !isset($this->presentMap[spl_object_hash($package)])) { + foreach ($this->presentMap as $presentPackage) { + if ($package->getName() == $presentPackage->getName() && $package->getVersion() == $presentPackage->getVersion() && $presentPackage->getSourceReference()) { + $package->setSourceDistReferences($presentPackage->getSourceReference()); + } + } + } $packages[] = $package; } } diff --git a/src/Composer/DependencyResolver/PoolBuilder.php b/src/Composer/DependencyResolver/PoolBuilder.php index 39ac5098f..1c995bc4e 100644 --- a/src/Composer/DependencyResolver/PoolBuilder.php +++ b/src/Composer/DependencyResolver/PoolBuilder.php @@ -184,7 +184,7 @@ class PoolBuilder if (isset($this->rootReferences[$name])) { // do not modify the references on already locked packages if (!$request->isFixedPackage($package)) { - $this->setReferences($package, $this->rootReferences[$name]); + $package->setSourceDistReferences($this->rootReferences[$name]); } } @@ -225,19 +225,5 @@ class PoolBuilder return $loadNames; } - - private function setReferences(Package $package, $reference) - { - $package->setSourceReference($reference); - - // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL - // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? - if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) { - $package->setDistReference($reference); - $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl())); - } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it - $package->setDistReference($reference); - } - } } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index dea1e03e5..86d251756 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -38,6 +38,7 @@ use Composer\Package\AliasPackage; use Composer\Package\BasePackage; use Composer\Package\CompletePackage; use Composer\Package\Link; +use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; use Composer\Package\Package; @@ -137,6 +138,7 @@ class Installer * * @var array|null */ + protected $updateMirrors = false; protected $updateWhitelist = null; protected $whitelistTransitiveDependencies = false; protected $whitelistAllDependencies = false; @@ -192,6 +194,10 @@ class Installer gc_collect_cycles(); gc_disable(); + if ($this->updateWhitelist && $this->updateMirrors) { + throw new \RuntimeException("The installer options updateMirrors and updateWhitelist are mutually exclusive."); + } + // Force update if there is no lock file present if (!$this->update && !$this->locker->isLocked()) { // TODO throw an error instead? @@ -370,8 +376,15 @@ class Installer $links = array_merge($this->package->getRequires(), $this->package->getDevRequires()); - foreach ($links as $link) { - $request->install($link->getTarget(), $link->getConstraint()); + // 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) { + foreach ($lockedRepository->getPackages() as $lockedPackage) { + $request->install($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion())); + } + } else { + foreach ($links as $link) { + $request->install($link->getTarget(), $link->getConstraint()); + } } // if the updateWhitelist is enabled, packages not in it are also fixed @@ -489,8 +502,8 @@ class Installer } $updatedLock = $this->locker->setLockData( - $lockTransaction->getNewLockPackages(false), - $lockTransaction->getNewLockPackages(true), + $lockTransaction->getNewLockPackages(false, $this->updateMirrors), + $lockTransaction->getNewLockPackages(true, $this->updateMirrors), $platformReqs, $platformDevReqs, $aliases, @@ -912,23 +925,6 @@ class Installer return $normalizedAliases; } - // TODO do we still need this function? - private function updateInstallReferences(PackageInterface $package, $reference) - { - if (!$reference) { - return; - } - - $package->setSourceReference($reference); - - if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) { - $package->setDistReference($reference); - $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl())); - } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it - $package->setDistReference($reference); - } - } - /** * @param PlatformRepository $platformRepo * @param array $aliases @@ -1044,7 +1040,7 @@ class Installer $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); } - if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { + if (count($depPackages) == 0 && !$nameMatchesRequiredPackage) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } @@ -1347,6 +1343,19 @@ class Installer return $this; } + /** + * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info + * + * @param bool $updateMirrors + * @return Installer + */ + public function setUpdateMirrors($updateMirrors) + { + $this->updateMirrors = $updateMirrors; + + return $this; + } + /** * restrict the update operation to a few packages, all other packages * that are already installed will be kept at their current version diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 89f197856..b103139dd 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -411,4 +411,9 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return $this->aliasOf->setDistType($type); } + + public function setSourceDistReferences($reference) + { + return $this->aliasOf->setSourceDistReferences($reference); + } } diff --git a/src/Composer/Package/Package.php b/src/Composer/Package/Package.php index 6c7b426e7..c633e1856 100644 --- a/src/Composer/Package/Package.php +++ b/src/Composer/Package/Package.php @@ -569,6 +569,23 @@ class Package extends BasePackage return $this->archiveExcludes; } + /** + * {@inheritDoc} + */ + public function setSourceDistReferences($reference) + { + $this->setSourceReference($reference); + + // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL + // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? + if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl())) { + $this->setDistReference($reference); + $this->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl())); + } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it + $this->setDistReference($reference); + } + } + /** * Replaces current version and pretty version with passed values. * It also sets stability. diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 25a2e9bfe..7e83839ff 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -386,4 +386,13 @@ interface PackageInterface * @return void */ public function setDistReference($reference); + + /** + * Set dist and source references and update dist URL for ones that contain a reference + * + * @param string $reference + * + * @return void + */ + public function setSourceDistReferences($reference); } diff --git a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test index 9d88870b0..9bfca4c85 100644 --- a/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-mirrors-changes-url.test @@ -149,14 +149,14 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "packages": [ { "name": "a/a", "version": "dev-master", - "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }, + "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/newa", "type": "git" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/newa/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { "name": "b/b", "version": "2.0.3", - "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" }, + "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/newb", "type": "git" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/newb/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, { @@ -171,12 +171,6 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" }, "type": "library" }, - { - "name": "e/e", "version": "dev-master", - "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/e/newe", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip" }, - "type": "library" - }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, @@ -185,8 +179,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an }, { "name": "g/g", "version": "dev-master", - "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/g/newg", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/newg", "type": "git" }, + "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/newg/zipball/0000000000000000000000000000000000000000", "type": "zip" }, "type": "library" } ], @@ -206,8 +200,5 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "platform-dev": [] } --RUN-- -update a/a b/b d/d g/g +update mirrors --EXPECT-- -Installing e/e (dev-master 1111111) -Updating a/a (dev-master 1111111) to a/a (dev-master 2222222) -Updating g/g (dev-master 0000000) to g/g (dev-master 1111111) diff --git a/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test index dfb3f650d..a82487a31 100644 --- a/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test +++ b/tests/Composer/Test/Fixtures/installer/update-picks-up-change-of-vcs-type.test @@ -42,7 +42,7 @@ Converting from one VCS type to another (including an URL change) should update "platform-dev": [] } --RUN-- -update +update mirrors --EXPECT-LOCK-- { "packages": [ diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index ce87d111f..f1e55a794 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -269,11 +269,19 @@ class InstallerTest extends TestCase }); $application->get('update')->setCode(function ($input, $output) use ($installer) { + $packages = $input->getArgument('packages'); + $filteredPackages = array_filter($packages, function ($package) { + return !in_array($package, array('lock', 'nothing', 'mirrors'), true); + }); + $updateMirrors = $input->getOption('lock') || count($filteredPackages) != count($packages); + $packages = $filteredPackages; + $installer ->setDevMode(!$input->getOption('no-dev')) ->setUpdate(true) ->setDryRun($input->getOption('dry-run')) - ->setUpdateWhitelist($input->getArgument('packages')) + ->setUpdateMirrors($updateMirrors) + ->setUpdateWhitelist($packages) ->setWhitelistTransitiveDependencies($input->getOption('with-dependencies')) ->setWhitelistAllDependencies($input->getOption('with-all-dependencies')) ->setPreferStable($input->getOption('prefer-stable'))