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