From 6059acf0a39fa97147fa8c26de95b4aa8ea1bcc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Wed, 27 Dec 2017 13:20:12 +0100 Subject: [PATCH 01/48] Fix: Remove unused parameter and field --- src/Composer/Factory.php | 2 +- src/Composer/Package/Locker.php | 5 +-- tests/Composer/Test/InstallerTest.php | 2 +- tests/Composer/Test/Package/LockerTest.php | 44 ++++++---------------- 4 files changed, 14 insertions(+), 39 deletions(-) diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index a68586fbb..e1c4cf95c 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -390,7 +390,7 @@ class Factory ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; - $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile)); + $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile)); $composer->setLocker($locker); } diff --git a/src/Composer/Package/Locker.php b/src/Composer/Package/Locker.php index 93e5ca655..e86115bfb 100644 --- a/src/Composer/Package/Locker.php +++ b/src/Composer/Package/Locker.php @@ -32,7 +32,6 @@ use Seld\JsonLint\ParsingException; class Locker { private $lockFile; - private $repositoryManager; private $installationManager; private $hash; private $contentHash; @@ -46,14 +45,12 @@ class Locker * * @param IOInterface $io * @param JsonFile $lockFile lockfile loader - * @param RepositoryManager $repositoryManager repository manager instance * @param InstallationManager $installationManager installation manager instance * @param string $composerFileContents The contents of the composer file */ - public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents) + public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, $composerFileContents) { $this->lockFile = $lockFile; - $this->repositoryManager = $repositoryManager; $this->installationManager = $installationManager; $this->hash = md5($composerFileContents); $this->contentHash = self::getContentHash($composerFileContents); diff --git a/tests/Composer/Test/InstallerTest.php b/tests/Composer/Test/InstallerTest.php index 468d8fbbd..fe17f2959 100644 --- a/tests/Composer/Test/InstallerTest.php +++ b/tests/Composer/Test/InstallerTest.php @@ -198,7 +198,7 @@ class InstallerTest extends TestCase } $contents = json_encode($composerConfig); - $locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents); + $locker = new Locker($io, $lockJsonMock, $composer->getInstallationManager(), $contents); $composer->setLocker($locker); $eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index a37e09907..63fbf4a1b 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -21,7 +21,7 @@ class LockerTest extends TestCase public function testIsLocked() { $json = $this->createJsonFileMock(); - $locker = new Locker(new NullIO, $json, $this->createRepositoryManagerMock(), $this->createInstallationManagerMock(), + $locker = new Locker(new NullIO, $json, $this->createInstallationManagerMock(), $this->getJsonContent()); $json @@ -39,10 +39,9 @@ class LockerTest extends TestCase public function testGetNotLockedPackages() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); $json ->expects($this->once()) @@ -57,10 +56,9 @@ class LockerTest extends TestCase public function testGetLockedPackages() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); $json ->expects($this->once()) @@ -84,11 +82,10 @@ class LockerTest extends TestCase public function testSetLockData() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); $jsonContent = $this->getJsonContent() . ' '; - $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); + $locker = new Locker(new NullIO, $json, $inst, $jsonContent); $package1 = $this->createPackageMock(); $package2 = $this->createPackageMock(); @@ -157,10 +154,9 @@ class LockerTest extends TestCase public function testLockBadPackages() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); $package1 = $this->createPackageMock(); $package1 @@ -176,11 +172,10 @@ class LockerTest extends TestCase public function testIsFresh() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); $jsonContent = $this->getJsonContent(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); + $locker = new Locker(new NullIO, $json, $inst, $jsonContent); $json ->expects($this->once()) @@ -193,10 +188,9 @@ class LockerTest extends TestCase public function testIsFreshFalse() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); $json ->expects($this->once()) @@ -209,11 +203,10 @@ class LockerTest extends TestCase public function testIsFreshWithContentHash() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); $jsonContent = $this->getJsonContent(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); + $locker = new Locker(new NullIO, $json, $inst, $jsonContent); $json ->expects($this->once()) @@ -226,11 +219,10 @@ class LockerTest extends TestCase public function testIsFreshWithContentHashAndNoHash() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); $jsonContent = $this->getJsonContent(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent); + $locker = new Locker(new NullIO, $json, $inst, $jsonContent); $json ->expects($this->once()) @@ -243,10 +235,9 @@ class LockerTest extends TestCase public function testIsFreshFalseWithContentHash() { $json = $this->createJsonFileMock(); - $repo = $this->createRepositoryManagerMock(); $inst = $this->createInstallationManagerMock(); - $locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent()); + $locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent()); $differentHash = md5($this->getJsonContent(array('name' => 'test2'))); @@ -264,20 +255,7 @@ class LockerTest extends TestCase ->disableOriginalConstructor() ->getMock(); } - - private function createRepositoryManagerMock() - { - $mock = $this->getMockBuilder('Composer\Repository\RepositoryManager') - ->disableOriginalConstructor() - ->getMock(); - - $mock->expects($this->any()) - ->method('getLocalRepository') - ->will($this->returnValue($this->getMockBuilder('Composer\Repository\ArrayRepository')->getMock())); - - return $mock; - } - + private function createInstallationManagerMock() { $mock = $this->getMockBuilder('Composer\Installer\InstallationManager') From 4d85e217c30ebcc67618e0978cd99544f7aeb634 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Sat, 16 Feb 2019 18:46:59 +0100 Subject: [PATCH 02/48] Extract the ZIP utility functions from ArtifactRepository --- .../Repository/ArtifactRepository.php | 66 +------------ src/Composer/Util/Zip.php | 96 +++++++++++++++++++ 2 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 src/Composer/Util/Zip.php diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 079d34c54..223ea4aef 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -16,6 +16,7 @@ use Composer\IO\IOInterface; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\LoaderInterface; +use Composer\Util\Zip; /** * @author Serge Smertin @@ -80,73 +81,14 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito } } - /** - * Find a file by name, returning the one that has the shortest path. - * - * @param \ZipArchive $zip - * @param string $filename - * @return bool|int - */ - private function locateFile(\ZipArchive $zip, $filename) - { - $indexOfShortestMatch = false; - $lengthOfShortestMatch = -1; - - for ($i = 0; $i < $zip->numFiles; $i++) { - $stat = $zip->statIndex($i); - if (strcmp(basename($stat['name']), $filename) === 0) { - $directoryName = dirname($stat['name']); - if ($directoryName == '.') { - //if composer.json is in root directory - //it has to be the one to use. - return $i; - } - - if (strpos($directoryName, '\\') !== false || - strpos($directoryName, '/') !== false) { - //composer.json files below first directory are rejected - continue; - } - - $length = strlen($stat['name']); - if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { - //Check it's not a directory. - $contents = $zip->getFromIndex($i); - if ($contents !== false) { - $indexOfShortestMatch = $i; - $lengthOfShortestMatch = $length; - } - } - } - } - - return $indexOfShortestMatch; - } - private function getComposerInformation(\SplFileInfo $file) { - $zip = new \ZipArchive(); - if ($zip->open($file->getPathname()) !== true) { + $composerFile = Zip::findFile($file->getPathname(), 'composer.json'); + + if (null === $composerFile) { return false; } - if (0 == $zip->numFiles) { - $zip->close(); - - return false; - } - - $foundFileIndex = $this->locateFile($zip, 'composer.json'); - if (false === $foundFileIndex) { - $zip->close(); - - return false; - } - - $configurationFileName = $zip->getNameIndex($foundFileIndex); - $zip->close(); - - $composerFile = "zip://{$file->getPathname()}#$configurationFileName"; $json = file_get_contents($composerFile); $package = JsonFile::parseJson($json, $composerFile); diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php new file mode 100644 index 000000000..1dfd99d39 --- /dev/null +++ b/src/Composer/Util/Zip.php @@ -0,0 +1,96 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Util; + +/** + * @author Jordi Boggiano + */ +class Zip +{ + /** + * Finds the path to a file inside a ZIP archive. + * + * @param $pathToZip + * @param $filename + * + * @return string|null + */ + public static function findFile($pathToZip, $filename) + { + $zip = new \ZipArchive(); + if ($zip->open($pathToZip) !== true) { + return null; + } + + if (0 == $zip->numFiles) { + $zip->close(); + + return null; + } + + $foundFileIndex = static::locateFile($zip, $filename); + if (false === $foundFileIndex) { + $zip->close(); + + return null; + } + + $configurationFileName = $zip->getNameIndex($foundFileIndex); + $zip->close(); + + return "zip://{$pathToZip}#$configurationFileName"; + } + + /** + * Find a file by name, returning the one that has the shortest path. + * + * @param \ZipArchive $zip + * @param string $filename + * @return bool|int + */ + private static function locateFile(\ZipArchive $zip, $filename) + { + $indexOfShortestMatch = false; + $lengthOfShortestMatch = -1; + + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + if (strcmp(basename($stat['name']), $filename) === 0) { + $directoryName = dirname($stat['name']); + if ($directoryName == '.') { + //if composer.json is in root directory + //it has to be the one to use. + return $i; + } + + if (strpos($directoryName, '\\') !== false || + strpos($directoryName, '/') !== false) { + //composer.json files below first directory are rejected + continue; + } + + $length = strlen($stat['name']); + if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) { + //Check it's not a directory. + $contents = $zip->getFromIndex($i); + if ($contents !== false) { + $indexOfShortestMatch = $i; + $lengthOfShortestMatch = $length; + } + } + } + } + + return $indexOfShortestMatch; + } +} From 3fc9ede24b85cbca0914060e6043abdc2cb1e406 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 18 Feb 2019 18:14:46 +0100 Subject: [PATCH 03/48] Add plugin callbacks for deactivation and uninstall, fixes #3000 --- src/Composer/Installer/PluginInstaller.php | 24 ++-- src/Composer/Plugin/PluginInterface.php | 18 +++ src/Composer/Plugin/PluginManager.php | 108 +++++++++++++++++- .../Fixtures/plugin-v1/Installer/Plugin.php | 11 ++ .../Fixtures/plugin-v2/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v3/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v4/Installer/Plugin1.php | 11 ++ .../Fixtures/plugin-v4/Installer/Plugin2.php | 11 ++ .../Fixtures/plugin-v5/Installer/Plugin5.php | 11 ++ .../Fixtures/plugin-v6/Installer/Plugin6.php | 11 ++ .../Fixtures/plugin-v7/Installer/Plugin7.php | 11 ++ .../Fixtures/plugin-v8/Installer/Plugin8.php | 11 ++ .../Fixtures/plugin-v9/Installer/Plugin.php | 11 ++ .../Test/Plugin/PluginInstallerTest.php | 32 +++++- 14 files changed, 282 insertions(+), 10 deletions(-) diff --git a/src/Composer/Installer/PluginInstaller.php b/src/Composer/Installer/PluginInstaller.php index 62a16fc62..a52e1937e 100644 --- a/src/Composer/Installer/PluginInstaller.php +++ b/src/Composer/Installer/PluginInstaller.php @@ -70,7 +70,7 @@ class PluginInstaller extends LibraryInstaller $this->composer->getPluginManager()->registerPackage($package, true); } catch (\Exception $e) { // Rollback installation - $this->io->writeError('Plugin installation failed, rolling back'); + $this->io->writeError('Plugin initialization failed, uninstalling plugin'); parent::uninstall($repo, $package); throw $e; } @@ -81,12 +81,22 @@ class PluginInstaller extends LibraryInstaller */ public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { - $extra = $target->getExtra(); - if (empty($extra['class'])) { - throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.'); - } - parent::update($repo, $initial, $target); - $this->composer->getPluginManager()->registerPackage($target, true); + + try { + $this->composer->getPluginManager()->deactivatePackage($initial, true); + $this->composer->getPluginManager()->registerPackage($target, true); + } catch (\Exception $e) { + // Rollback installation + $this->io->writeError('Plugin initialization failed, uninstalling plugin'); + parent::uninstall($repo, $target); + throw $e; + } + } + + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->composer->getPluginManager()->uninstallPackage($package, true); + parent::uninstall($repo, $package); } } diff --git a/src/Composer/Plugin/PluginInterface.php b/src/Composer/Plugin/PluginInterface.php index 5158b66f6..27b8c9754 100644 --- a/src/Composer/Plugin/PluginInterface.php +++ b/src/Composer/Plugin/PluginInterface.php @@ -36,4 +36,22 @@ interface PluginInterface * @param IOInterface $io */ public function activate(Composer $composer, IOInterface $io); + + /** + * Remove any hooks from Composer + * + * @param Composer $composer + * @param IOInterface $io + */ + public function deactivate(Composer $composer, IOInterface $io); + + /** + * Prepare the plugin to be uninstalled + * + * This will be called after deactivate + * + * @param Composer $composer + * @param IOInterface $io + */ + public function uninstall(Composer $composer, IOInterface $io); } diff --git a/src/Composer/Plugin/PluginManager.php b/src/Composer/Plugin/PluginManager.php index 786d846c5..7408359bd 100644 --- a/src/Composer/Plugin/PluginManager.php +++ b/src/Composer/Plugin/PluginManager.php @@ -144,7 +144,7 @@ class PluginManager $oldInstallerPlugin = ($package->getType() === 'composer-installer'); - if (in_array($package->getName(), $this->registeredPlugins)) { + if (isset($this->registeredPlugins[$package->getName()])) { return; } @@ -200,16 +200,82 @@ class PluginManager if ($oldInstallerPlugin) { $installer = new $class($this->io, $this->composer); $this->composer->getInstallationManager()->addInstaller($installer); + $this->registeredPlugins[$package->getName()] = $installer; } elseif (class_exists($class)) { $plugin = new $class(); $this->addPlugin($plugin); - $this->registeredPlugins[] = $package->getName(); + $this->registeredPlugins[$package->getName()] = $plugin; } elseif ($failOnMissingClasses) { throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class); } } } + /** + * Deactivates a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @param PackageInterface $package + * + * @throws \UnexpectedValueException + */ + public function deactivatePackage(PackageInterface $package) + { + if ($this->disablePlugins) { + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + if ($oldInstallerPlugin) { + $installer = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->composer->getInstallationManager()->removeInstaller($installer); + } else { + $plugin = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->removePlugin($plugin); + } + } + + /** + * Uninstall a plugin package + * + * If it's of type composer-installer it is unregistered from the installers + * instead for BC + * + * @param PackageInterface $package + * + * @throws \UnexpectedValueException + */ + public function uninstallPackage(PackageInterface $package) + { + if ($this->disablePlugins) { + return; + } + + $oldInstallerPlugin = ($package->getType() === 'composer-installer'); + + if (!isset($this->registeredPlugins[$package->getName()])) { + return; + } + + if ($oldInstallerPlugin) { + $this->deactivatePackage($package); + } else { + $plugin = $this->registeredPlugins[$package->getName()]; + unset($this->registeredPlugins[$package->getName()]); + $this->removePlugin($plugin); + $this->uninstallPlugin($plugin); + } + } + /** * Returns the version of the internal composer-plugin-api package. * @@ -240,6 +306,44 @@ class PluginManager } } + /** + * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance + * + * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function removePlugin(PluginInterface $plugin) + { + $index = array_search($plugin, $this->plugins, true); + if ($index === false) { + return; + } + + $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG); + unset($this->plugins[$index]); + $plugin->deactivate($this->composer, $this->io); + + $this->composer->getEventDispatcher()->removeListener($plugin); + } + + /** + * Notifies a plugin it is being uninstalled and should clean up + * + * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer + * programmatically and want to deregister a plugin class directly this is a valid way + * to do it. + * + * @param PluginInterface $plugin plugin instance + */ + public function uninstallPlugin(PluginInterface $plugin) + { + $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG); + $plugin->uninstall($this->composer, $this->io); + } + /** * Load all plugins and installers from a repository * diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php index f80acd325..c757d4b09 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v1/Installer/Plugin.php @@ -12,5 +12,16 @@ class Plugin implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v1'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v1'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v1'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php index db5a4462e..32090b66d 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v2/Installer/Plugin2.php @@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v2'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v2'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v2'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php index 861c1679b..034388162 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v3/Installer/Plugin2.php @@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v3'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v3'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v3'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php index 93bcabc98..2eaee6a3f 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin1.php @@ -13,5 +13,16 @@ class Plugin1 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v4-plugin1'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v4-plugin1'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v4-plugin1'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php index d946deb89..3c5311a82 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v4/Installer/Plugin2.php @@ -13,5 +13,16 @@ class Plugin2 implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v4-plugin2'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v4-plugin2'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v4-plugin2'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php index a2ac37bc5..fb9f08a6d 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v5/Installer/Plugin5.php @@ -10,5 +10,16 @@ class Plugin5 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v5'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v5'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v5'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php index e46c0fcb0..acce1f972 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v6/Installer/Plugin6.php @@ -10,5 +10,16 @@ class Plugin6 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v6'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v6'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v6'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php index 5560a6047..84734ce3b 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v7/Installer/Plugin7.php @@ -10,5 +10,16 @@ class Plugin7 implements PluginInterface { public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v7'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v7'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v7'); } } diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php index 7e9a0aab1..4534e13ef 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v8/Installer/Plugin8.php @@ -13,6 +13,17 @@ class Plugin8 implements PluginInterface, Capable public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v8'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v8'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v8'); } public function getCapabilities() diff --git a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php index 74e1beb8b..870f11cd1 100644 --- a/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php +++ b/tests/Composer/Test/Plugin/Fixtures/plugin-v9/Installer/Plugin.php @@ -14,5 +14,16 @@ class Plugin implements PluginInterface public function activate(Composer $composer, IOInterface $io) { + $io->write('activate v9'); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + $io->write('deactivate v9'); + } + + public function uninstall(Composer $composer, IOInterface $io) + { + $io->write('uninstall v9'); } } diff --git a/tests/Composer/Test/Plugin/PluginInstallerTest.php b/tests/Composer/Test/Plugin/PluginInstallerTest.php index 633c5ab18..bd83ce16f 100644 --- a/tests/Composer/Test/Plugin/PluginInstallerTest.php +++ b/tests/Composer/Test/Plugin/PluginInstallerTest.php @@ -19,6 +19,9 @@ use Composer\Package\CompletePackage; use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\ArrayLoader; use Composer\Plugin\PluginManager; +use Symfony\Component\Console\Output\OutputInterface; +use Composer\IO\BufferIO; +use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Test\TestCase; use Composer\Util\Filesystem; @@ -96,7 +99,7 @@ class PluginInstallerTest extends TestCase return __DIR__.'/Fixtures/'.$package->getPrettyName(); })); - $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $this->io = new BufferIO(); $dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock(); $this->autoloadGenerator = new AutoloadGenerator($dispatcher); @@ -108,6 +111,7 @@ class PluginInstallerTest extends TestCase $this->composer->setRepositoryManager($rm); $this->composer->setInstallationManager($im); $this->composer->setAutoloadGenerator($this->autoloadGenerator); + $this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io)); $this->pm = new PluginManager($this->io, $this->composer); $this->composer->setPluginManager($this->pm); @@ -140,6 +144,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertEquals('installer-v1', $plugins[0]->version); + $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } public function testInstallMultiplePlugins() @@ -158,6 +163,7 @@ class PluginInstallerTest extends TestCase $this->assertEquals('installer-v4', $plugins[0]->version); $this->assertEquals('plugin2', $plugins[1]->name); $this->assertEquals('installer-v4', $plugins[1]->version); + $this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithNewClassName() @@ -176,7 +182,29 @@ class PluginInstallerTest extends TestCase $installer->update($this->repository, $this->packages[0], $this->packages[1]); $plugins = $this->pm->getPlugins(); + $this->assertCount(1, $plugins); $this->assertEquals('installer-v2', $plugins[1]->version); + $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput()); + } + + public function testUninstall() + { + $this->repository + ->expects($this->once()) + ->method('getPackages') + ->will($this->returnValue(array($this->packages[0]))); + $this->repository + ->expects($this->exactly(1)) + ->method('hasPackage') + ->will($this->onConsecutiveCalls(true, false)); + $installer = new PluginInstaller($this->io, $this->composer); + $this->pm->loadInstalledPlugins(); + + $installer->uninstall($this->repository, $this->packages[0]); + + $plugins = $this->pm->getPlugins(); + $this->assertCount(0, $plugins); + $this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput()); } public function testUpgradeWithSameClassName() @@ -196,6 +224,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertEquals('installer-v3', $plugins[1]->version); + $this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput()); } public function testRegisterPluginOnlyOneTime() @@ -213,6 +242,7 @@ class PluginInstallerTest extends TestCase $plugins = $this->pm->getPlugins(); $this->assertCount(1, $plugins); $this->assertEquals('installer-v1', $plugins[0]->version); + $this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput()); } /** From 60df8925174dfb385368efbbfd2d19c7f372c2cd Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 21 Feb 2019 12:27:02 +0100 Subject: [PATCH 04/48] Store dev mode in installed.json, fixes #3008 --- .travis.yml | 2 +- src/Composer/Installer.php | 4 ++-- src/Composer/Repository/FilesystemRepository.php | 15 ++++++++++----- .../Repository/WritableArrayRepository.php | 2 +- .../Repository/WritableRepositoryInterface.php | 4 +++- .../Mock/InstalledFilesystemRepositoryMock.php | 2 +- .../Test/Repository/FilesystemRepositoryTest.php | 5 +++-- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index a06922904..c732f9421 100644 --- a/.travis.yml +++ b/.travis.yml @@ -62,7 +62,7 @@ script: - ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);' # Run PHPStan - if [[ $PHPSTAN == "1" ]]; then - composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs && + bin/composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs && vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php; fi diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index fc877f18e..4dddd9505 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -616,7 +616,7 @@ class Installer } if ($this->executeOperations || $this->writeLock) { - $localRepo->write(); + $localRepo->write($this->devMode); } $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); @@ -628,7 +628,7 @@ class Installer if ($this->executeOperations) { // force source/dist urls to be updated for all packages $this->processPackageUrls($pool, $policy, $localRepo, $repositories); - $localRepo->write(); + $localRepo->write($this->devMode); } return array(0, $devPackages); diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index bde55aad3..9dbac5f76 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -49,7 +49,12 @@ class FilesystemRepository extends WritableArrayRepository } try { - $packages = $this->file->read(); + $data = $this->file->read(); + if (isset($data['packages'])) { + $packages = $data['packages']; + } else { + $packages = $data; + } if (!is_array($packages)) { throw new \UnexpectedValueException('Could not parse package list from the repository'); @@ -74,16 +79,16 @@ class FilesystemRepository extends WritableArrayRepository /** * Writes writable repository. */ - public function write() + public function write($devMode) { - $data = array(); + $data = array('packages' => array(), 'dev' => $devMode); $dumper = new ArrayDumper(); foreach ($this->getCanonicalPackages() as $package) { - $data[] = $dumper->dump($package); + $data['packages'][] = $dumper->dump($package); } - usort($data, function ($a, $b) { + usort($data['packages'], function ($a, $b) { return strcmp($a['name'], $b['name']); }); diff --git a/src/Composer/Repository/WritableArrayRepository.php b/src/Composer/Repository/WritableArrayRepository.php index 041e40562..284f9bcb0 100644 --- a/src/Composer/Repository/WritableArrayRepository.php +++ b/src/Composer/Repository/WritableArrayRepository.php @@ -24,7 +24,7 @@ class WritableArrayRepository extends ArrayRepository implements WritableReposit /** * {@inheritDoc} */ - public function write() + public function write($devMode) { } diff --git a/src/Composer/Repository/WritableRepositoryInterface.php b/src/Composer/Repository/WritableRepositoryInterface.php index 4500005d9..4fb3d0c66 100644 --- a/src/Composer/Repository/WritableRepositoryInterface.php +++ b/src/Composer/Repository/WritableRepositoryInterface.php @@ -23,8 +23,10 @@ interface WritableRepositoryInterface extends RepositoryInterface { /** * Writes repository (f.e. to the disc). + * + * @param bool $devMode Whether dev requirements were included or not in this installation */ - public function write(); + public function write($devMode); /** * Adds package to the repository. diff --git a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php index 9c11dc307..8c8c280e8 100644 --- a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php +++ b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php @@ -20,7 +20,7 @@ class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository { } - public function write() + public function write($devMode) { } } diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index be8b0d0a9..4d8d7c103 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -95,11 +95,12 @@ class FilesystemRepositoryTest extends TestCase ->expects($this->once()) ->method('write') ->with(array( - array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0'), + 'packages' => array(array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0')), + 'dev' => true, )); $repository->addPackage($this->getPackage('mypkg', '0.1.10')); - $repository->write(); + $repository->write(true); } private function createJsonFileMock() From 9de07bed1b3f4b96e0da90f2caab525073cf5048 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 25 Feb 2019 08:01:38 +0100 Subject: [PATCH 05/48] Fixed docblocks --- src/Composer/Util/Zip.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 1dfd99d39..a14eea924 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -20,8 +20,8 @@ class Zip /** * Finds the path to a file inside a ZIP archive. * - * @param $pathToZip - * @param $filename + * @param string $pathToZip + * @param string $filename * * @return string|null */ @@ -55,7 +55,8 @@ class Zip * Find a file by name, returning the one that has the shortest path. * * @param \ZipArchive $zip - * @param string $filename + * @param string $filename + * * @return bool|int */ private static function locateFile(\ZipArchive $zip, $filename) From 05d6b2178542857131011e3e46a8165414abc8aa Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 25 Feb 2019 08:02:04 +0100 Subject: [PATCH 06/48] Use self:: for private method --- src/Composer/Util/Zip.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index a14eea924..2e0333ac0 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -38,7 +38,7 @@ class Zip return null; } - $foundFileIndex = static::locateFile($zip, $filename); + $foundFileIndex = self::locateFile($zip, $filename); if (false === $foundFileIndex) { $zip->close(); From 0d0cb53f314eb7422f16b479e2efd145c730362f Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Fri, 1 Mar 2019 11:06:03 +0100 Subject: [PATCH 07/48] Adjust Zip Util to only find the root composer.json --- src/Composer/Repository/ArtifactRepository.php | 2 +- src/Composer/Util/Zip.php | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 223ea4aef..2358f9205 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -83,7 +83,7 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { - $composerFile = Zip::findFile($file->getPathname(), 'composer.json'); + $composerFile = Zip::findComposerJson($file->getPathname()); if (null === $composerFile) { return false; diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 2e0333ac0..fcad76604 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -18,15 +18,19 @@ namespace Composer\Util; class Zip { /** - * Finds the path to a file inside a ZIP archive. + * Finds the path to the root composer.json inside a ZIP archive. * * @param string $pathToZip * @param string $filename * * @return string|null */ - public static function findFile($pathToZip, $filename) + public static function findComposerJson($pathToZip) { + if (!extension_loaded('zip')) { + throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); + } + $zip = new \ZipArchive(); if ($zip->open($pathToZip) !== true) { return null; @@ -38,7 +42,7 @@ class Zip return null; } - $foundFileIndex = self::locateFile($zip, $filename); + $foundFileIndex = self::locateFile($zip, 'composer.json'); if (false === $foundFileIndex) { $zip->close(); @@ -68,7 +72,7 @@ class Zip $stat = $zip->statIndex($i); if (strcmp(basename($stat['name']), $filename) === 0) { $directoryName = dirname($stat['name']); - if ($directoryName == '.') { + if ($directoryName === '.') { //if composer.json is in root directory //it has to be the one to use. return $i; From a91fd20673843e40e7d692f5f40fe3132912b030 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 4 Mar 2019 09:54:35 +0100 Subject: [PATCH 08/48] Return the composer.json content instead of a zip:// path --- src/Composer/Repository/ArtifactRepository.php | 8 +++----- src/Composer/Util/Zip.php | 13 ++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Composer/Repository/ArtifactRepository.php b/src/Composer/Repository/ArtifactRepository.php index 2358f9205..aff80e4cd 100644 --- a/src/Composer/Repository/ArtifactRepository.php +++ b/src/Composer/Repository/ArtifactRepository.php @@ -83,15 +83,13 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito private function getComposerInformation(\SplFileInfo $file) { - $composerFile = Zip::findComposerJson($file->getPathname()); + $json = Zip::getComposerJson($file->getPathname()); - if (null === $composerFile) { + if (null === $json) { return false; } - $json = file_get_contents($composerFile); - - $package = JsonFile::parseJson($json, $composerFile); + $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json'); $package['dist'] = array( 'type' => 'zip', 'url' => strtr($file->getPathname(), '\\', '/'), diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index fcad76604..6698616ff 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -18,14 +18,14 @@ namespace Composer\Util; class Zip { /** - * Finds the path to the root composer.json inside a ZIP archive. + * Gets content of the root composer.json inside a ZIP archive. * * @param string $pathToZip * @param string $filename * * @return string|null */ - public static function findComposerJson($pathToZip) + public static function getComposerJson($pathToZip) { if (!extension_loaded('zip')) { throw new \RuntimeException('The Zip Util requires PHP\'s zip extension'); @@ -49,10 +49,17 @@ class Zip return null; } + $content = null; $configurationFileName = $zip->getNameIndex($foundFileIndex); + $stream = $zip->getStream($configurationFileName); + + if (false !== $stream) { + $content = stream_get_contents($stream); + } + $zip->close(); - return "zip://{$pathToZip}#$configurationFileName"; + return $content; } /** From 0e2215dc6c40c67ca720fe4863eaff5624d8ead1 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 4 Mar 2019 11:08:59 +0100 Subject: [PATCH 09/48] Added full unit test coverage --- src/Composer/Util/Zip.php | 2 +- .../Composer/Test/Util/Fixtures/Zip/empty.zip | Bin 0 -> 22 bytes .../Test/Util/Fixtures/Zip/folder.zip | Bin 0 -> 314 bytes .../Test/Util/Fixtures/Zip/multiple.zip | Bin 0 -> 2569 bytes .../Test/Util/Fixtures/Zip/nojson.zip | Bin 0 -> 134 bytes .../Composer/Test/Util/Fixtures/Zip/root.zip | Bin 0 -> 194 bytes .../Test/Util/Fixtures/Zip/subfolder.zip | Bin 0 -> 1328 bytes tests/Composer/Test/Util/ZipTest.php | 117 ++++++++++++++++++ 8 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/empty.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/folder.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/multiple.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/nojson.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/root.zip create mode 100644 tests/Composer/Test/Util/Fixtures/Zip/subfolder.zip create mode 100644 tests/Composer/Test/Util/ZipTest.php diff --git a/src/Composer/Util/Zip.php b/src/Composer/Util/Zip.php index 6698616ff..8c79d106c 100644 --- a/src/Composer/Util/Zip.php +++ b/src/Composer/Util/Zip.php @@ -13,7 +13,7 @@ namespace Composer\Util; /** - * @author Jordi Boggiano + * @author Andreas Schempp */ class Zip { diff --git a/tests/Composer/Test/Util/Fixtures/Zip/empty.zip b/tests/Composer/Test/Util/Fixtures/Zip/empty.zip new file mode 100644 index 0000000000000000000000000000000000000000..15cb0ecb3e219d1701294bfdf0fe3f5cb5d208e7 GIT binary patch literal 22 NcmWIWW@Tf*000g10H*)| literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/folder.zip b/tests/Composer/Test/Util/Fixtures/Zip/folder.zip new file mode 100644 index 0000000000000000000000000000000000000000..72b17b542f11eaaf47e413832e50a5538eb75e9c GIT binary patch literal 314 zcmWIWW@h1H0D+>Q6hANnO0X~pFr?+@>xV}0Fsyl76SD${zcPw21ORo2FmM22Fq#fQ zsE*|P+=Be#)FQpC;`}_A_B^Qe)z5+$n3nE2awkMpn|0}yKQ(`s98pqT7o`U@n4P0O zuXwu@&;cME;LXS+%8bi#JTSL9ymbUIAx`ChI~AfE;ZS6g1sM>!moyqdb)z{OVid^P T0p6@^AS;-FuoFnn25}ewNhC+U literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip b/tests/Composer/Test/Util/Fixtures/Zip/multiple.zip new file mode 100644 index 0000000000000000000000000000000000000000..db8c50302d78e7512c043c4598fd62f518af151c GIT binary patch literal 2569 zcmWIWW@h1H0D9J_iOYJ9Y%ed5Eum*72cZaN; zaO1hyoCT}rHJO$_(7EzMR7prBVnNskyOY0^Ds%kJwjF24`F0_sf&IaR+?UBr$ITWk zIBoJHw=et7;;j7DKSRxJuOFOPWPgm;YD-4jPyO)b75;*!*R2!J-G1ZSj^|&WJ^lFQ zWsTOVpKe!P@t|$qy1u1*_P$Z zvM=ps&z`pNhrzl%+Hq%qa`wMc760+NN7~K(FCKsWzf*^O&(yDb<~`i2ATjaWtqr-; zcQZc7?E9#?;6#6G`i~d*(ws90894P&B}z0J}C&6u`A1#~!j)?6D_-u2nA{XGA6^h$g2eBqefhbjzQbzgzyynQ}!*S#@2M9@x2TPq(|s0i6oM2s&*) zb0=Up7d^*_qBz%3A0@y+84i(itoaTZ2)Ly8a(kQTY2Gbh$V+fN$+?4pHGIMMi;A}d zH;DuVs5Vtj`Wn_%pDE+daO1;{_y!(@gT2OflWvH$tTQ{O*dpN6QJcK3h17he(UnQoQ2wV@9g5>*eL1`PU;LD zxtoANhd-%<0s$q>LZTfJj)c>$0xl!a(lBz6QY{TbQX+byKuw8;`WPt@3CQR8j6inDU&pP;Mj(3#VGP1U$c32%iibX+8-tP#vHK0ZDC0pg@vUPu z6C{8UMIyv|pt1}(ph2b}ms*lYrYvdPie?I0I^lp9kH{g0p0ZILrp1ixFi@t2I1H4u zksSsq`m&J915p%*rJ$LDk`iFK7THngDFNBIzmAzKNGSmnjff%zJyl?jP6ZU#t-)m+ zTC}1%6k;+c^pG>h~n5SP$Mynf|UUQ-mGk({K^Z2 L*MQ+J3+4d;JZ$R> literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip b/tests/Composer/Test/Util/Fixtures/Zip/nojson.zip new file mode 100644 index 0000000000000000000000000000000000000000..e536b956ce1f7c30410c96e53079a7a272bee0d6 GIT binary patch literal 134 zcmWIWW@h1H0D;-TDSluElwe^HU`Wf)*AI>0VYvOiCgvIte`OS52=HcP5@p7vhX-ba d!&^rX6Ji1f+=KvcRyL40BM{mFX(JGa0RSi#7c&3= literal 0 HcmV?d00001 diff --git a/tests/Composer/Test/Util/Fixtures/Zip/root.zip b/tests/Composer/Test/Util/Fixtures/Zip/root.zip new file mode 100644 index 0000000000000000000000000000000000000000..fd08f4d34dca94997e0a368500e9db6d9458ee46 GIT binary patch literal 194 zcmWIWW@Zs#-~htlpcFp_B*4ocz>u7uTaaIzTBMg%oSzpO!NV~BZB0xb5PxM9VOaev zh=FP8o+EccRJB=`ZuwL5cgYbY)pb#N3<2Kk9QAp{+ogcUfpCB~Ba9<*fOmYO^o~FaN4*AxTAik* z2{)dL%~_!BcW{<YzU)LgyQ-2i#pKZKtBI5c|F|oE_%ktS7iS*FE=|M50!NPqi(^6jtqz$w;lm-d`6t`T9;Jrwua zFSm-5p}cqtKcnZLNQ*=1`#}c0c612b^@tNyd=1r@oS$2eUz}Q`msOmf2TR3y zZy|xW`dJVI)6zXh?u4jnvo788r{?dHBTB04qV&KHt + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Util; + +use Composer\Util\Zip; +use PHPUnit\Framework\TestCase; + +/** + * @author Andreas Schempp + */ +class ZipTest extends TestCase +{ + public function testThrowsExceptionIfZipExcentionIsNotLoaded() + { + if (extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is loaded.'); + } + + $this->setExpectedException('\RuntimeException', 'The Zip Util requires PHP\'s zip extension'); + + Zip::getComposerJson(''); + } + + public function testReturnsNullifTheZipIsNotFound() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipIsEmpty() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheZipHasNoComposerJson() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip'); + + $this->assertNull($result); + } + + public function testReturnsNullIfTheComposerJsonIsInASubSubfolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip'); + + $this->assertNull($result); + } + + public function testReturnsComposerJsonInZipRoot() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsComposerJsonInFirstFolder() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } + + public function testReturnsRootComposerJsonAndSkipsSubfolders() + { + if (!extension_loaded('zip')) { + $this->markTestSkipped('The PHP zip extension is not loaded.'); + return; + } + + $result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip'); + + $this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result); + } +} From 81a4f74b5b78beadaea6443973951fbe6827720a Mon Sep 17 00:00:00 2001 From: Ken Love Date: Wed, 12 Jun 2019 16:54:09 -0400 Subject: [PATCH 10/48] Composer\Script\Event should have access to originating event details --- .../EventDispatcher/EventDispatcher.php | 4 ++- src/Composer/Script/Event.php | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index ee02381e5..d5bdb4c97 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/src/Composer/EventDispatcher/EventDispatcher.php @@ -196,7 +196,9 @@ class EventDispatcher } try { - $return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags)); + $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags); + $scriptEvent->setOriginatingEvent($event); + $return = $this->dispatch($scriptName, $scriptEvent); } catch (ScriptExecutionException $e) { $this->io->writeError(sprintf('Script %s was called via %s', $callable, $event->getName()), true, IOInterface::QUIET); throw $e; diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index 138f43c1a..bbad249b2 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -39,6 +39,11 @@ class Event extends BaseEvent */ private $devMode; + /** + * @var BaseEvent + */ + private $originatingEvent; + /** * Constructor. * @@ -55,6 +60,7 @@ class Event extends BaseEvent $this->composer = $composer; $this->io = $io; $this->devMode = $devMode; + $this->originatingEvent = null; } /** @@ -86,4 +92,27 @@ class Event extends BaseEvent { return $this->devMode; } + + /** + * Set the originating event. + * + * @return \Composer\EventDispatcher\Event|null + */ + public function getOriginatingEvent() + { + return $this->originatingEvent; + } + + /** + * Get the originating event. + * + * @param \Composer\EventDispatcher\Event $event + * @return $this + */ + public function setOriginatingEvent(BaseEvent $event) + { + $this->originatingEvent = $event; + + return $this; + } } From b51cfce8e6983671b9b732ce88ae603f298378d1 Mon Sep 17 00:00:00 2001 From: Ken Love Date: Thu, 13 Jun 2019 14:51:27 -0400 Subject: [PATCH 11/48] return the upper-most event in chain --- src/Composer/Script/Event.php | 29 +++++++--- tests/Composer/Test/Script/EventTest.php | 73 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 tests/Composer/Test/Script/EventTest.php diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index bbad249b2..21aeb83ed 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -103,16 +103,31 @@ class Event extends BaseEvent return $this->originatingEvent; } - /** - * Get the originating event. - * - * @param \Composer\EventDispatcher\Event $event - * @return $this - */ + /** + * Set the originating event. + * + * @param \Composer\EventDispatcher\Event $event + * @return $this + */ public function setOriginatingEvent(BaseEvent $event) { - $this->originatingEvent = $event; + $this->originatingEvent = $this->calculateOriginatingEvent($event); return $this; } + + /** + * Returns the upper-most event in chain. + * + * @param \Composer\EventDispatcher\Event $event + * @return \Composer\EventDispatcher\Event + */ + private function calculateOriginatingEvent(BaseEvent $event) + { + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } + + return $event; + } } diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php new file mode 100644 index 000000000..2b8818500 --- /dev/null +++ b/tests/Composer/Test/Script/EventTest.php @@ -0,0 +1,73 @@ +getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); + + $scriptEvent = new Event('test', $composer, $io, true); + + $this->assertNull( + $scriptEvent->getOriginatingEvent(), + 'originatingEvent is initialized as null' + ); + + $scriptEvent->setOriginatingEvent($originatingEvent); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return test event' + ); + + } + + public function testEventCalculatesNestedOriginatingEvent() { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); + + + $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); + $intermediateEvent = new Event('intermediate', $composer, $io, true); + $intermediateEvent->setOriginatingEvent($originatingEvent); + + $scriptEvent = new Event('test', $composer, $io, true); + $scriptEvent->setOriginatingEvent($intermediateEvent); + + $this->assertNotSame( + $intermediateEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD NOT return intermediate events' + ); + + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return upper-most event' + ); + + } + + private function createComposerInstance() + { + $composer = new Composer; + $config = new Config; + $composer->setConfig($config); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $composer->setPackage($package); + + return $composer; + } +} \ No newline at end of file From 8def53c4b3cd7ef09d35ee566b92cfafb4e55421 Mon Sep 17 00:00:00 2001 From: kpitn Date: Tue, 9 Jul 2019 15:29:21 +0200 Subject: [PATCH 12/48] Update only one package This PR come with https://github.com/composer/satis/pull/509 --- .../handling-private-packages-with-satis.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/articles/handling-private-packages-with-satis.md b/doc/articles/handling-private-packages-with-satis.md index cdf31f6e4..3ef604fe7 100644 --- a/doc/articles/handling-private-packages-with-satis.md +++ b/doc/articles/handling-private-packages-with-satis.md @@ -112,6 +112,19 @@ Note that this will still need to pull and scan all of your VCS repositories because any VCS repository might contain (on any branch) one of the selected packages. +If you want to scan only the selected package and not all VCS repositories you need +to declare a *name* for all your package (this only work on VCS repositories type) : + +```json +{ + "repositories": [ + { "name": "company/privaterepo", "type": "vcs", "url": "https://github.com/mycompany/privaterepo" }, + { "name": "private/repo", "type": "vcs", "url": "http://svn.example.org/private/repo" }, + { "name": "mycompany/privaterepo2", "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" } + ] +} +``` + If you want to scan only a single repository and update all packages found in it, pass the VCS repository URL as an optional argument: From a4611d511fcd7719f3343e20058b04b3c39d200f Mon Sep 17 00:00:00 2001 From: Baptiste Lafontaine Date: Thu, 11 Jul 2019 16:48:57 +0200 Subject: [PATCH 13/48] Ignore platform reqs now handle conflict rules --- src/Composer/DependencyResolver/RuleSetGenerator.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Composer/DependencyResolver/RuleSetGenerator.php b/src/Composer/DependencyResolver/RuleSetGenerator.php index 6117c1d95..e8714a405 100644 --- a/src/Composer/DependencyResolver/RuleSetGenerator.php +++ b/src/Composer/DependencyResolver/RuleSetGenerator.php @@ -233,7 +233,7 @@ class RuleSetGenerator } } - protected function addConflictRules() + protected function addConflictRules($ignorePlatformReqs = false) { /** @var PackageInterface $package */ foreach ($this->addedPackages as $package) { @@ -242,6 +242,10 @@ class RuleSetGenerator continue; } + if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) { + continue; + } + /** @var PackageInterface $possibleConflict */ foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) { $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true); @@ -362,7 +366,7 @@ class RuleSetGenerator $this->addRulesForJobs($ignorePlatformReqs); - $this->addConflictRules(); + $this->addConflictRules($ignorePlatformReqs); // Remove references to packages $this->addedPackages = $this->addedPackagesByNames = null; From b935d1c8129f4f97e7d7a1cc9e5eea64a1d32890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=BDurek?= Date: Fri, 12 Jul 2019 18:34:12 +0200 Subject: [PATCH 14/48] fixed phpstan error --- tests/Composer/Test/Repository/ComposerRepositoryTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 47df3a443..22a58cf4f 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -227,7 +227,9 @@ class ComposerRepositoryTest extends TestCase $repository = new ComposerRepository( array('url' => $repositoryUrl), new NullIO(), - FactoryMock::createConfig() + FactoryMock::createConfig(), + $this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock() ); $object = new \ReflectionObject($repository); From fedc69f828d138d13eeb3afc32969ec93c497e90 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Sun, 14 Jul 2019 15:25:01 +0300 Subject: [PATCH 15/48] add php 7.4snapshot --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f02fefcb1..c1b0db05b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ matrix: - php: 7.3 env: deps=high - php: nightly + - php: 7.4snapshot fast_finish: true allow_failures: - php: nightly From 54cd1290cbe56ee26244bef9e1c534435fb5b296 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Tue, 16 Jul 2019 14:41:39 +0300 Subject: [PATCH 16/48] add to allow_failures --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c1b0db05b..2e3270b65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ matrix: fast_finish: true allow_failures: - php: nightly + - php: 7.4snapshot before_install: # disable xdebug if available From b4fc3b7eefc6c6437ca1da7815848abf6ae07568 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:39:40 +0200 Subject: [PATCH 17/48] Make usage of foreach to improve readability Instead of count and comparing, we can simple use a foreach. --- tests/Composer/Test/AllFunctionalTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Composer/Test/AllFunctionalTest.php b/tests/Composer/Test/AllFunctionalTest.php index 7a3ef3ee0..5e8ebb5c4 100644 --- a/tests/Composer/Test/AllFunctionalTest.php +++ b/tests/Composer/Test/AllFunctionalTest.php @@ -162,18 +162,18 @@ class AllFunctionalTest extends TestCase } }; - for ($i = 0, $c = count($tokens); $i < $c; $i++) { - if ('' === $tokens[$i] && null === $section) { + foreach ($tokens as $token) { + if ('' === $token && null === $section) { continue; } // Handle section headers. if (null === $section) { - $section = $tokens[$i]; + $section = $token; continue; } - $sectionData = $tokens[$i]; + $sectionData = $token; // Allow sections to validate, or modify their section data. switch ($section) { From 6c8ddd4d57a2cf36568bdc5353d00cdad73a90eb Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:48:48 +0200 Subject: [PATCH 18/48] Remove unused private properties --- src/Composer/Util/TlsHelper.php | 2 -- tests/Composer/Test/Downloader/ZipDownloaderTest.php | 1 - tests/Composer/Test/Util/GitHubTest.php | 3 --- tests/Composer/Test/Util/GitLabTest.php | 1 - 4 files changed, 7 deletions(-) diff --git a/src/Composer/Util/TlsHelper.php b/src/Composer/Util/TlsHelper.php index 34336d06c..a53212f2d 100644 --- a/src/Composer/Util/TlsHelper.php +++ b/src/Composer/Util/TlsHelper.php @@ -19,8 +19,6 @@ use Composer\CaBundle\CaBundle; */ final class TlsHelper { - private static $useOpensslParse; - /** * Match hostname against a certificate. * diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index 466fd35c7..239ec9221 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -23,7 +23,6 @@ class ZipDownloaderTest extends TestCase * @var string */ private $testDir; - private $prophet; private $io; private $config; diff --git a/tests/Composer/Test/Util/GitHubTest.php b/tests/Composer/Test/Util/GitHubTest.php index 28d00ce69..9be1307e2 100644 --- a/tests/Composer/Test/Util/GitHubTest.php +++ b/tests/Composer/Test/Util/GitHubTest.php @@ -23,12 +23,9 @@ use RecursiveIteratorIterator; */ class GitHubTest extends TestCase { - private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'github.com'; - private $token = 'githubtoken'; public function testUsernamePasswordAuthenticationFlow() { diff --git a/tests/Composer/Test/Util/GitLabTest.php b/tests/Composer/Test/Util/GitLabTest.php index 27f46b4ad..8a1b97af0 100644 --- a/tests/Composer/Test/Util/GitLabTest.php +++ b/tests/Composer/Test/Util/GitLabTest.php @@ -23,7 +23,6 @@ class GitLabTest extends TestCase { private $username = 'username'; private $password = 'password'; - private $authcode = 'authcode'; private $message = 'mymessage'; private $origin = 'gitlab.com'; private $token = 'gitlabtoken'; From 4cb2b303ec3bc9b709cb47d398593da3f558067d Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 02:57:08 +0200 Subject: [PATCH 19/48] Remove override assignment --- tests/Composer/Test/Package/Loader/ArrayLoaderTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php index e3b9dc491..ca1cec0db 100644 --- a/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php +++ b/tests/Composer/Test/Package/Loader/ArrayLoaderTest.php @@ -148,7 +148,6 @@ class ArrayLoaderTest extends TestCase { $package = $this->loader->load($config); $dumper = new ArrayDumper; - $expectedConfig = $config; $expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config); $this->assertEquals($expectedConfig, $dumper->dump($package)); } From 8b5be1d08c74926d25a740691d45e3afe7b780bd Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 03:07:25 +0200 Subject: [PATCH 20/48] Remove explicts void returns --- src/Composer/Downloader/PerforceDownloader.php | 2 -- src/Composer/Util/Perforce.php | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index a472b84c6..92091e2c9 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -87,8 +87,6 @@ class PerforceDownloader extends VcsDownloader public function getLocalChanges(PackageInterface $package, $path) { $this->io->writeError('Perforce driver does not check for local changes before overriding', true); - - return; } /** diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index 31ddeffec..52080d663 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -363,8 +363,6 @@ class Perforce while ($line !== false) { $line = fgets($pipe); } - - return; } public function windowsLogin($password) From 1d05d4171c1179a0fbeccef8c22d40492bca8c85 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 24 Jul 2019 03:01:00 +0200 Subject: [PATCH 21/48] Remove unused private methods --- tests/Composer/Test/DependencyResolver/RuleSetTest.php | 7 ------- tests/Composer/Test/Repository/Vcs/FossilDriverTest.php | 9 --------- tests/Composer/Test/Repository/Vcs/SvnDriverTest.php | 9 --------- 3 files changed, 25 deletions(-) diff --git a/tests/Composer/Test/DependencyResolver/RuleSetTest.php b/tests/Composer/Test/DependencyResolver/RuleSetTest.php index bd6efbc1b..22c1c15d0 100644 --- a/tests/Composer/Test/DependencyResolver/RuleSetTest.php +++ b/tests/Composer/Test/DependencyResolver/RuleSetTest.php @@ -157,11 +157,4 @@ class RuleSetTest extends TestCase $this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($this->pool)); } - - private function getRuleMock() - { - return $this->getMockBuilder('Composer\DependencyResolver\Rule') - ->disableOriginalConstructor() - ->getMock(); - } } diff --git a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php index cbb4342e9..f39a3b8f7 100644 --- a/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/FossilDriverTest.php @@ -40,15 +40,6 @@ class FossilDriverTest extends TestCase $fs->removeDirectory($this->home); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( diff --git a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php index 029d20160..4ef0d9bcc 100644 --- a/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/SvnDriverTest.php @@ -70,15 +70,6 @@ class SvnDriverTest extends TestCase $svn->initialize(); } - private function getCmd($cmd) - { - if (Platform::isWindows()) { - return strtr($cmd, "'", '"'); - } - - return $cmd; - } - public static function supportProvider() { return array( From 45591597f6e239c59d11ad7303e2d50220144469 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 16:37:35 +0200 Subject: [PATCH 22/48] Clarify how check-platform-reqs works, fixes #8191 --- doc/03-cli.md | 4 ++++ src/Composer/Command/CheckPlatformReqsCommand.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/doc/03-cli.md b/doc/03-cli.md index 49879d54d..8cfd5cac0 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -259,6 +259,10 @@ match the platform requirements of the installed packages. This can be used to verify that a production server has all the extensions needed to run a project after installing it for example. +Unlike update/install, this command will ignore config.platform settings and +check the real platform packages so you can be certain you have the required +platform dependencies. + ## global The global command allows you to run other commands like `install`, `remove`, `require` diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index de7f4b4d3..8ce0b0186 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -34,6 +34,8 @@ class CheckPlatformReqsCommand extends BaseCommand <<php composer.phar check-platform-reqs EOT From 369e8a22474a9a85da43c6b627c89c1fa0b6fd0e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 16:43:24 +0200 Subject: [PATCH 23/48] Fix indenting --- src/Composer/Script/Event.php | 20 ++--- tests/Composer/Test/Script/EventTest.php | 109 ++++++++++++----------- 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/src/Composer/Script/Event.php b/src/Composer/Script/Event.php index 21aeb83ed..5fab172bf 100644 --- a/src/Composer/Script/Event.php +++ b/src/Composer/Script/Event.php @@ -39,9 +39,9 @@ class Event extends BaseEvent */ private $devMode; - /** - * @var BaseEvent - */ + /** + * @var BaseEvent + */ private $originatingEvent; /** @@ -100,7 +100,7 @@ class Event extends BaseEvent */ public function getOriginatingEvent() { - return $this->originatingEvent; + return $this->originatingEvent; } /** @@ -111,9 +111,9 @@ class Event extends BaseEvent */ public function setOriginatingEvent(BaseEvent $event) { - $this->originatingEvent = $this->calculateOriginatingEvent($event); + $this->originatingEvent = $this->calculateOriginatingEvent($event); - return $this; + return $this; } /** @@ -124,10 +124,10 @@ class Event extends BaseEvent */ private function calculateOriginatingEvent(BaseEvent $event) { - if ($event instanceof Event && $event->getOriginatingEvent()) { - return $this->calculateOriginatingEvent($event->getOriginatingEvent()); - } + if ($event instanceof Event && $event->getOriginatingEvent()) { + return $this->calculateOriginatingEvent($event->getOriginatingEvent()); + } - return $event; + return $event; } } diff --git a/tests/Composer/Test/Script/EventTest.php b/tests/Composer/Test/Script/EventTest.php index 2b8818500..b7c8cd9ff 100644 --- a/tests/Composer/Test/Script/EventTest.php +++ b/tests/Composer/Test/Script/EventTest.php @@ -1,73 +1,80 @@ + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ namespace Composer\Test\Script; - use Composer\Composer; use Composer\Config; use Composer\Script\Event; use Composer\Test\TestCase; -class EventTest extends TestCase { +class EventTest extends TestCase +{ + public function testEventSetsOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); - public function testEventSetsOriginatingEvent() { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $composer = $this->createComposerInstance(); + $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); - $originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent'); + $scriptEvent = new Event('test', $composer, $io, true); - $scriptEvent = new Event('test', $composer, $io, true); + $this->assertNull( + $scriptEvent->getOriginatingEvent(), + 'originatingEvent is initialized as null' + ); - $this->assertNull( - $scriptEvent->getOriginatingEvent(), - 'originatingEvent is initialized as null' - ); + $scriptEvent->setOriginatingEvent($originatingEvent); - $scriptEvent->setOriginatingEvent($originatingEvent); + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return test event' + ); + } - $this->assertSame( - $originatingEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD return test event' - ); + public function testEventCalculatesNestedOriginatingEvent() + { + $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); + $composer = $this->createComposerInstance(); - } + $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); + $intermediateEvent = new Event('intermediate', $composer, $io, true); + $intermediateEvent->setOriginatingEvent($originatingEvent); - public function testEventCalculatesNestedOriginatingEvent() { - $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); - $composer = $this->createComposerInstance(); + $scriptEvent = new Event('test', $composer, $io, true); + $scriptEvent->setOriginatingEvent($intermediateEvent); + $this->assertNotSame( + $intermediateEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD NOT return intermediate events' + ); - $originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent'); - $intermediateEvent = new Event('intermediate', $composer, $io, true); - $intermediateEvent->setOriginatingEvent($originatingEvent); + $this->assertSame( + $originatingEvent, + $scriptEvent->getOriginatingEvent(), + 'getOriginatingEvent() SHOULD return upper-most event' + ); + } - $scriptEvent = new Event('test', $composer, $io, true); - $scriptEvent->setOriginatingEvent($intermediateEvent); + private function createComposerInstance() + { + $composer = new Composer; + $config = new Config; + $composer->setConfig($config); + $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); + $composer->setPackage($package); - $this->assertNotSame( - $intermediateEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD NOT return intermediate events' - ); - - $this->assertSame( - $originatingEvent, - $scriptEvent->getOriginatingEvent(), - 'getOriginatingEvent() SHOULD return upper-most event' - ); - - } - - private function createComposerInstance() - { - $composer = new Composer; - $config = new Config; - $composer->setConfig($config); - $package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock(); - $composer->setPackage($package); - - return $composer; - } -} \ No newline at end of file + return $composer; + } +} From 33759d02c4d730860dd31e949207fc43601caef5 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 17:42:34 +0200 Subject: [PATCH 24/48] Fix require command to allow working on network mounts, fixes #8231 --- src/Composer/Command/RequireCommand.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 508514eb4..9b12941b8 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -26,6 +26,7 @@ use Composer\Plugin\PluginEvents; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\IO\IOInterface; +use Composer\Util\Silencer; /** * @author Jérémy Romey @@ -103,11 +104,6 @@ EOT return 1; } - if (!is_writable($this->file)) { - $io->writeError(''.$this->file.' is not writable.'); - - return 1; - } if (filesize($this->file) === 0) { file_put_contents($this->file, "{\n}\n"); @@ -116,6 +112,14 @@ EOT $this->json = new JsonFile($this->file); $this->composerBackup = file_get_contents($this->json->getPath()); + // check for writability by writing to the file as is_writable can not be trusted on network-mounts + // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) { + $io->writeError(''.$this->file.' is not writable.'); + + return 1; + } + $composer = $this->getComposer(true, $input->getOption('no-plugins')); $repos = $composer->getRepositoryManager()->getRepositories(); From 8958f40f94239320f9849b2c3cfe31cad2f6b40c Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 29 Jul 2019 17:57:18 +0200 Subject: [PATCH 25/48] Make sure resetting composer also resets the IO and configuration, fixes #8224 --- src/Composer/Console/Application.php | 3 +++ src/Composer/IO/BaseIO.php | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index fe5dbaccf..d7d113492 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -373,6 +373,9 @@ class Application extends BaseApplication public function resetComposer() { $this->composer = null; + if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) { + $this->getIO()->resetAuthentications(); + } } /** diff --git a/src/Composer/IO/BaseIO.php b/src/Composer/IO/BaseIO.php index b327f1bbf..8f61c863d 100644 --- a/src/Composer/IO/BaseIO.php +++ b/src/Composer/IO/BaseIO.php @@ -29,6 +29,14 @@ abstract class BaseIO implements IOInterface, LoggerInterface return $this->authentications; } + /** + * {@inheritDoc} + */ + public function resetAuthentications() + { + $this->authentications = array(); + } + /** * {@inheritDoc} */ From 1a391b572cc82b681ce9f52b6db6aa87718fb137 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 09:18:19 +0200 Subject: [PATCH 26/48] Prevent require command from allowing a package to require itself, fixes #8247 --- src/Composer/Command/RequireCommand.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/RequireCommand.php b/src/Composer/Command/RequireCommand.php index 9b12941b8..8f91b6675 100644 --- a/src/Composer/Command/RequireCommand.php +++ b/src/Composer/Command/RequireCommand.php @@ -145,7 +145,12 @@ EOT // validate requirements format $versionParser = new VersionParser(); - foreach ($requirements as $constraint) { + foreach ($requirements as $package => $constraint) { + if (strtolower($package) === $composer->getPackage()->getName()) { + $io->writeError(sprintf('Root package \'%s\' cannot require itself in its composer.json', $package)); + + return 1; + } $versionParser->parseConstraints($constraint); } From 14f2a6dd9accf8a0b4807876eaf47b80bec1d034 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 09:48:49 +0200 Subject: [PATCH 27/48] Fix remove command not working with escaped slashes (e.g. foo\/bar), fixes #8249 --- src/Composer/Json/JsonManipulator.php | 5 +++-- tests/Composer/Test/Json/JsonManipulatorTest.php | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Composer/Json/JsonManipulator.php b/src/Composer/Json/JsonManipulator.php index 8fe6a9f0a..e64a56f71 100644 --- a/src/Composer/Json/JsonManipulator.php +++ b/src/Composer/Json/JsonManipulator.php @@ -326,9 +326,10 @@ class JsonManipulator } // try and find a match for the subkey - if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i', $children)) { + $keyRegex = str_replace('/', '\\\\?/', preg_quote($name)); + if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) { // find best match for the value of "name" - if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x', $children, $matches)) { + if (preg_match_all('{'.self::$DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) { $bestMatch = ''; foreach ($matches[0] as $match) { if (strlen($bestMatch) < strlen($match)) { diff --git a/tests/Composer/Test/Json/JsonManipulatorTest.php b/tests/Composer/Test/Json/JsonManipulatorTest.php index d8bc7c200..8bc7831af 100644 --- a/tests/Composer/Test/Json/JsonManipulatorTest.php +++ b/tests/Composer/Test/Json/JsonManipulatorTest.php @@ -1448,6 +1448,22 @@ class JsonManipulatorTest extends TestCase "repositories": { } } +', + ), + 'works on simple ones escaped slash' => array( + '{ + "repositories": { + "foo\/bar": { + "bar": "baz" + } + } +}', + 'foo/bar', + true, + '{ + "repositories": { + } +} ', ), 'works on simple ones middle' => array( From 3f5e4f0399ba108029b77500ab0cbf13fa19d75b Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 30 Jul 2019 10:58:36 +0200 Subject: [PATCH 28/48] Add support for defining a {"packagist.org":false} repo in composer init, fixes #8210 --- src/Composer/Command/InitCommand.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 66a56f978..6cd722ad5 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -168,13 +168,25 @@ EOT if ($repositories) { $config = Factory::createConfig($io); $repos = array(new PlatformRepository); + $createDefaultPackagistRepo = true; foreach ($repositories as $repo) { - $repos[] = RepositoryFactory::fromString($io, $config, $repo); + $repoConfig = RepositoryFactory::configFromString($io, $config, $repo); + if ( + (isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false)) + || (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false)) + ) { + $createDefaultPackagistRepo = false; + continue; + } + $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig); + } + + if ($createDefaultPackagistRepo) { + $repos[] = RepositoryFactory::createRepo($io, $config, array( + 'type' => 'composer', + 'url' => 'https://repo.packagist.org', + )); } - $repos[] = RepositoryFactory::createRepo($io, $config, array( - 'type' => 'composer', - 'url' => 'https://repo.packagist.org', - )); $this->repos = new CompositeRepository($repos); unset($repos, $config, $repositories); From 3e66d0514a69b4057c02685b633dff97a3917a19 Mon Sep 17 00:00:00 2001 From: Thomas Perez Date: Tue, 30 Jul 2019 18:45:41 +0200 Subject: [PATCH 29/48] Fix error_handler return type declaration --- src/Composer/Util/ErrorHandler.php | 3 +++ src/Composer/Util/RemoteFilesystem.php | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/Composer/Util/ErrorHandler.php b/src/Composer/Util/ErrorHandler.php index 83e6b5ede..c4dabd1d7 100644 --- a/src/Composer/Util/ErrorHandler.php +++ b/src/Composer/Util/ErrorHandler.php @@ -33,6 +33,7 @@ class ErrorHandler * * @static * @throws \ErrorException + * @return bool */ public static function handle($level, $message, $file, $line) { @@ -63,6 +64,8 @@ class ErrorHandler }, array_slice(debug_backtrace(), 2)))); } } + + return true; } /** diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index e1a93fcf9..2b66585db 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -321,6 +321,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); + + return true; }); try { $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header); @@ -494,6 +496,8 @@ class RemoteFilesystem $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg); + + return true; }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); From 5ddc40e93ce49955013553d985fe47f3b341fd01 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 15:21:32 +0200 Subject: [PATCH 30/48] Load packages from the lock file for check-platform-reqs if no dependencies have been installed yet, fixes #8058 --- src/Composer/Command/CheckPlatformReqsCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Composer/Command/CheckPlatformReqsCommand.php b/src/Composer/Command/CheckPlatformReqsCommand.php index 8ce0b0186..195a2c490 100644 --- a/src/Composer/Command/CheckPlatformReqsCommand.php +++ b/src/Composer/Command/CheckPlatformReqsCommand.php @@ -51,6 +51,10 @@ EOT $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages(); } else { $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); + // fallback to lockfile if installed repo is empty + if (!$dependencies) { + $dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages(); + } $requires += $composer->getPackage()->getDevRequires(); } foreach ($requires as $require => $link) { From 0261ce809279a4789c8bd882097c4d59c10475f8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 17:41:18 +0200 Subject: [PATCH 31/48] Improve handling of non-standard ports for GitLab and GitHub installs, fixes #8173 --- src/Composer/Downloader/FileDownloader.php | 4 +-- .../Repository/ComposerRepository.php | 12 ++++---- src/Composer/Repository/Vcs/GitHubDriver.php | 4 +++ src/Composer/Repository/Vcs/GitLabDriver.php | 28 +++++++++---------- src/Composer/Util/GitLab.php | 11 +++++++- src/Composer/Util/RemoteFilesystem.php | 13 +++++++++ 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index e63df021a..819fbcefb 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -125,7 +125,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $fileName = $this->getFileName($package, $path); $processedUrl = $this->processUrl($package, $url); - $hostname = parse_url($processedUrl, PHP_URL_HOST); + $origin = RemoteFilesystem::getOrigin($processedUrl); $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl); if ($this->eventDispatcher) { @@ -150,7 +150,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $retries = 3; while ($retries--) { try { - $rfs->copy($hostname, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); + $rfs->copy($origin, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions()); break; } catch (TransportException $e) { // if we got an http response with a proper code, then requesting again will probably not help, abort diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 9d5b727cc..c595b20c2 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -206,8 +206,8 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) { $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl); - $hostname = parse_url($url, PHP_URL_HOST) ?: $url; - $json = $this->rfs->getContents($hostname, $url, false); + $origin = RemoteFilesystem::getOrigin($url); + $json = $this->rfs->getContents($origin, $url, false); $search = JsonFile::parseJson($json, $url); if (empty($search['results'])) { @@ -681,10 +681,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; + $origin = RemoteFilesystem::getOrigin($filename); $rfs = $preFileDownloadEvent->getRemoteFilesystem(); - $json = $rfs->getContents($hostname, $filename, false); + $json = $rfs->getContents($origin, $filename, false); if ($sha256 && $sha256 !== hash('sha256', $json)) { // undo downgrade before trying again if http seems to be hijacked or modifying content somehow if ($this->allowSslDowngrade) { @@ -760,10 +760,10 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent); } - $hostname = parse_url($filename, PHP_URL_HOST) ?: $filename; + $origin = RemoteFilesystem::getOrigin($filename); $rfs = $preFileDownloadEvent->getRemoteFilesystem(); $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime))); - $json = $rfs->getContents($hostname, $filename, false, $options); + $json = $rfs->getContents($origin, $filename, false, $options); if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) { return true; } diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 8c051bd50..529112811 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -304,6 +304,10 @@ class GitHubDriver extends VcsDriver */ protected function generateSshUrl() { + if (false !== strpos($this->originUrl, ':')) { + return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git'; } diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 2044ff702..7b593084f 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -67,9 +67,9 @@ class GitLabDriver extends VcsDriver private $isPrivate = true; /** - * @var int port number + * @var bool true if the origin has a port number or a path component in it */ - protected $portNumber; + private $hasNonstandardOrigin = false; const URL_REGEX = '#^(?:(?Phttps?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; @@ -94,11 +94,10 @@ class GitLabDriver extends VcsDriver ? $match['scheme'] : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https') ; - $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts); + $this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']); - if (!empty($match['port']) && true === is_numeric($match['port'])) { - // If it is an HTTP based URL, and it has a port - $this->portNumber = (int) $match['port']; + if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) { + $this->hasNonstandardOrigin = true; } $this->namespace = implode('/', $urlParts); @@ -259,10 +258,7 @@ class GitLabDriver extends VcsDriver */ public function getApiUrl() { - $domainName = $this->originUrl; - $portNumber = (true === is_numeric($this->portNumber)) ? sprintf(':%s', $this->portNumber) : ''; - - return $this->scheme.'://'.$domainName.$portNumber.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); + return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository); } /** @@ -360,6 +356,10 @@ class GitLabDriver extends VcsDriver */ protected function generateSshUrl() { + if ($this->hasNonstandardOrigin) { + return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git'; + } + return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git'; } @@ -458,7 +458,7 @@ class GitLabDriver extends VcsDriver $guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2']; $urlParts = explode('/', $match['parts']); - if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) { + if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) { return false; } @@ -492,16 +492,16 @@ class GitLabDriver extends VcsDriver * @param array $urlParts * @return bool|string */ - private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts) + private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) { - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { return $guessedDomain; } while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; - if (in_array($guessedDomain, $configuredDomains)) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) { return $guessedDomain; } } diff --git a/src/Composer/Util/GitLab.php b/src/Composer/Util/GitLab.php index 475c5e7ee..7a69ad251 100644 --- a/src/Composer/Util/GitLab.php +++ b/src/Composer/Util/GitLab.php @@ -53,7 +53,10 @@ class GitLab */ public function authorizeOAuth($originUrl) { - if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) { + // before composer 1.9, origin URLs had no port number in them + $bcOriginUrl = preg_replace('{:\d+}', '', $originUrl); + + if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) { return false; } @@ -73,6 +76,12 @@ class GitLab return true; } + if (isset($authTokens[$bcOriginUrl])) { + $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token'); + + return true; + } + return false; } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 2b66585db..32767c161 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -1110,4 +1110,17 @@ class RemoteFilesystem $io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].''); } } + + public static function getOrigin($urlOrPath) + { + $hostPort = parse_url($urlOrPath, PHP_URL_HOST); + if (!$hostPort) { + return $urlOrPath; + } + if (parse_url($urlOrPath, PHP_URL_PORT)) { + $hostPort .= ':'.parse_url($urlOrPath, PHP_URL_PORT); + } + + return $hostPort; + } } From 0fe200d6d950b0774688713be3153bb410eb70b8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:01:08 +0200 Subject: [PATCH 32/48] Fix origin computation --- src/Composer/Repository/Vcs/GitLabDriver.php | 9 ++++++++- tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitLabDriver.php b/src/Composer/Repository/Vcs/GitLabDriver.php index 7b593084f..0d8925bc8 100644 --- a/src/Composer/Repository/Vcs/GitLabDriver.php +++ b/src/Composer/Repository/Vcs/GitLabDriver.php @@ -495,13 +495,20 @@ class GitLabDriver extends VcsDriver private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) { if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { + if ($portNumber) { + return $guessedDomain.':'.$portNumber; + } return $guessedDomain; } + if ($portNumber) { + $guessedDomain .= ':'.$portNumber; + } + while (null !== ($part = array_shift($urlParts))) { $guessedDomain .= '/' . $part; - if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{/}', ':'.$portNumber.'/', $guessedDomain, 1), $configuredDomains))) { + if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{:\d+}', '', $guessedDomain), $configuredDomains))) { return $guessedDomain; } } diff --git a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php index a5eb799f2..81e623182 100644 --- a/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php +++ b/tests/Composer/Test/Repository/Vcs/GitLabDriverTest.php @@ -207,7 +207,7 @@ JSON; JSON; $this->remoteFilesystem - ->getContents($domain, $apiUrl, false, array()) + ->getContents($domain.':'.$port, $apiUrl, false, array()) ->willReturn(sprintf($projectData, $domain, $port, $namespace)) ->shouldBeCalledTimes(1); From db6882b57fec3721bd0e35ceb41c62a7b81ff8bf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:36:27 +0200 Subject: [PATCH 33/48] Fix updating or URLs to include dist type and shasum, fixes #8216 --- src/Composer/Installer.php | 6 ++- .../installer/update-changes-url.test | 40 +++++++++---------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 9583c3eb1..f3bfc5363 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl()); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); @@ -1190,7 +1190,7 @@ class Installer } } - private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl) + private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl, $distType, $distShaSum) { $oldSourceRef = $package->getSourceReference(); @@ -1204,6 +1204,8 @@ class Installer // but for other urls this is ambiguous and could result in bad outcomes if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) { $package->setDistUrl($distUrl); + $package->setDistType($distType); + $package->setDistSha1Checksum($distShaSum); $this->updateInstallReferences($package, $sourceReference); } diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index d774ea188..b199f90d4 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -17,37 +17,37 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "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" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "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" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/c/newc", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "d/d", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/d/newd", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "e/e", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/e/newe", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "f/f", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/g/newg", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/zipball/2222222222222222222222222222222222222222", "type": "zip" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } } ] } @@ -67,32 +67,32 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "a/a", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "b/b", "version": "2.0.3", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "d/d", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" }, - "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" } + "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" } } ] --EXPECT-LOCK-- @@ -101,43 +101,43 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "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" }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, "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" }, + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "c/c", "version": "1.0.0", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/newc", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/newc/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "d/d", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/newd", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "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" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/zipball/1111111111111111111111111111111111111111", "type": "zip" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" }, { "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" }, + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, "type": "library" } ], From 362ebe4f68ac397b3294110c4944884e9bd90495 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Wed, 31 Jul 2019 18:43:18 +0200 Subject: [PATCH 34/48] Fix update mirrors to also update transport-options, fixes #7672 --- src/Composer/Installer.php | 3 ++- .../Test/Fixtures/installer/update-changes-url.test | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index f3bfc5363..010e56ddc 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum(), $newPackage); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); @@ -1186,6 +1186,7 @@ class Installer $package->setDistMirrors($newPackage->getDistMirrors()); $package->setSourceMirrors($newPackage->getSourceMirrors()); + $package->setTransportOptions($newPackage->getTransportOptions()); } } } diff --git a/tests/Composer/Test/Fixtures/installer/update-changes-url.test b/tests/Composer/Test/Fixtures/installer/update-changes-url.test index b199f90d4..0a0d47507 100644 --- a/tests/Composer/Test/Fixtures/installer/update-changes-url.test +++ b/tests/Composer/Test/Fixtures/installer/update-changes-url.test @@ -42,7 +42,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "f/f", "version": "dev-master", "source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" }, - "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" } + "dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }, + "transport-options": { "foo": "bar2" } }, { "name": "g/g", "version": "dev-master", @@ -87,12 +88,14 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an { "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" }, - "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" } + "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }, + "transport-options": { "foo": "bar" } }, { "name": "g/g", "version": "dev-master", "source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" }, - "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" } + "dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" }, + "transport-options": { "foo": "bar" } } ] --EXPECT-LOCK-- @@ -132,7 +135,8 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an "name": "f/f", "version": "dev-master", "source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/newf", "type": "git" }, "dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/newf/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" }, - "type": "library" + "type": "library", + "transport-options": { "foo": "bar2" } }, { "name": "g/g", "version": "dev-master", From b5014e9aed08bd143f0f6944a156665fb86841ff Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 12:19:12 +0200 Subject: [PATCH 35/48] Fix use of decodeJson --- src/Composer/Command/DiagnoseCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index c2123e066..dee37502f 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -254,7 +254,7 @@ EOT $protocol = extension_loaded('openssl') ? 'https' : 'http'; try { - $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->parseJson(); + $json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson(); $hash = reset($json['provider-includes']); $hash = $hash['sha256']; $path = str_replace('%hash%', $hash, key($json['provider-includes'])); @@ -375,7 +375,7 @@ EOT } $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit'; - $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->parseJson(); + $data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->decodeJson(); return $data['resources']['core']; } From 872604ab36b00acb64dd97691c0ccfc77500f3d8 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 13:23:03 +0200 Subject: [PATCH 36/48] Allow path repos to point to their own source dir as install target, resulting in noop, fixes #8254 --- src/Composer/Downloader/PathDownloader.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 4c71fb4f4..5c4cbcf61 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -49,6 +49,18 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter )); } + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(sprintf( + ' - Installing %s (%s): Source already present', + $package->getName(), + $package->getFullPrettyVersion() + )); + } + + return; + } + if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours. // @@ -146,6 +158,16 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter */ public function remove(PackageInterface $package, $path, $output = true) { + $realUrl = realpath($package->getDistUrl()); + + if (realpath($path) === $realUrl) { + if ($output) { + $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "), source is still present in $path"); + } + + return; + } + /** * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which From 96ad0aa01fa49a39539c089ca4a1aa1a54cba98d Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 13:45:43 +0200 Subject: [PATCH 37/48] Remove extra arg --- src/Composer/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 010e56ddc..11edcbe72 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1178,7 +1178,7 @@ class Installer $newReference = $rootRefs[$package->getName()]; } - $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum(), $newPackage); + $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum()); if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) { $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned()); From 76a2c63bf8f2b77f68d1b4252781450d06981487 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 14:00:34 +0200 Subject: [PATCH 38/48] Show best possible version in diagnose command --- src/Composer/Command/DiagnoseCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Command/DiagnoseCommand.php b/src/Composer/Command/DiagnoseCommand.php index dee37502f..3bcc665dc 100644 --- a/src/Composer/Command/DiagnoseCommand.php +++ b/src/Composer/Command/DiagnoseCommand.php @@ -156,7 +156,7 @@ EOT $this->outputResult($this->checkVersion($config)); } - $io->write(sprintf('Composer version: %s', Composer::VERSION)); + $io->write(sprintf('Composer version: %s', Composer::getVersion())); $platformOverrides = $config->get('platform') ?: array(); $platformRepo = new PlatformRepository(array(), $platformOverrides); From f7c1b04a6ccbcf23ed8b8450162abe58da6aa216 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 14:26:42 +0200 Subject: [PATCH 39/48] Improve output when installing packages --- src/Composer/Downloader/ArchiveDownloader.php | 8 +++----- src/Composer/Downloader/PathDownloader.php | 8 ++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 3c53a086e..96aad4b9a 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -33,16 +33,14 @@ abstract class ArchiveDownloader extends FileDownloader public function install(PackageInterface $package, $path, $output = true) { if ($output) { - $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); + $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): Extracting archive"); + } else { + $this->io->writeError('Extracting archive', false); } $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $fileName = $this->getFileName($package, $path); - if ($output) { - $this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE); - } - try { $this->filesystem->ensureDirectoryExists($temporaryDir); try { diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index 23c51398e..e289c0173 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -82,6 +82,8 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $package->getName(), $package->getFullPrettyVersion() )); + } else { + $this->io->writeError('Source already present', false); } return; @@ -163,7 +165,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter $fileSystem->mirror($realUrl, $path, $iterator); } - $this->io->writeError(''); + if ($output) { + $this->io->writeError(''); + } } /** @@ -173,7 +177,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter { $realUrl = realpath($package->getDistUrl()); - if (realpath($path) === $realUrl) { + if ($path === $realUrl) { if ($output) { $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "), source is still present in $path"); } From 898ba6f869c49eb86d2d94c3f3719df05b259301 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 15:53:10 +0200 Subject: [PATCH 40/48] Only empty dir before actually installing packages, fixes #7929 --- src/Composer/Downloader/ArchiveDownloader.php | 2 ++ src/Composer/Downloader/FileDownloader.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 96aad4b9a..be863f1d3 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -38,6 +38,8 @@ abstract class ArchiveDownloader extends FileDownloader $this->io->writeError('Extracting archive', false); } + $this->filesystem->emptyDirectory($path); + $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8); $fileName = $this->getFileName($package, $path); diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 2c66c23a3..da2955638 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -101,7 +101,6 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface ); } - $this->filesystem->emptyDirectory($path); $fileName = $this->getFileName($package, $path); $io = $this->io; @@ -229,6 +228,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ")"); } + $this->filesystem->emptyDirectory($path); $this->filesystem->ensureDirectoryExists($path); $this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME)); } From 6a7220fed8b6fa00ad4dd962ac147593b0616131 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 15:57:33 +0200 Subject: [PATCH 41/48] Avoid wiping the whole target package if download of the new one fails, refs #7929 --- src/Composer/Downloader/FileDownloader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index da2955638..6a08bd67d 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -175,7 +175,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) { // clean up - $filesystem->removeDirectory($path); + if (file_exists($fileName)) { + $filesystem->unlink($fileName); + } $self->clearLastCacheWrite($package); if ($e instanceof TransportException) { From 9ee345ed29985e4e5b1142bf4442910aab8fa684 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 16:33:39 +0200 Subject: [PATCH 42/48] Make sure the directory exists and will not block installation later when downloading --- src/Composer/Downloader/FileDownloader.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 6a08bd67d..ab72bbaed 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -101,6 +101,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface ); } + $this->filesystem->ensureDirectoryExists($path); $fileName = $this->getFileName($package, $path); $io = $this->io; From 675f75c4b47ac4cf2fa9d15c9e4fd14fceed7d1f Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 16:41:06 +0200 Subject: [PATCH 43/48] Remove unnecessary config from phpstan --- phpstan/config.neon | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan/config.neon b/phpstan/config.neon index 9fd155963..0c5dc30b8 100644 --- a/phpstan/config.neon +++ b/phpstan/config.neon @@ -16,7 +16,6 @@ parameters: - '~^Anonymous function has an unused use \$io\.$~' - '~^Anonymous function has an unused use \$cache\.$~' - '~^Anonymous function has an unused use \$path\.$~' - - '~^Anonymous function has an unused use \$fileName\.$~' # ion cube is not installed - '~^Function ioncube_loader_\w+ not found\.$~' From 63da7c6b2d164cf264cfe456780009ea5119d4f9 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 21:39:26 +0200 Subject: [PATCH 44/48] Add install-path to the installed.json for every package, fixes #2174, closes #2424 --- src/Composer/Installer.php | 4 ++-- src/Composer/Repository/FilesystemRepository.php | 11 +++++++++-- .../Repository/WritableArrayRepository.php | 3 ++- .../Repository/WritableRepositoryInterface.php | 3 ++- .../Mock/InstalledFilesystemRepositoryMock.php | 3 ++- tests/Composer/Test/Package/LockerTest.php | 4 ++-- .../Test/Repository/FilesystemRepositoryTest.php | 14 ++++++++++++-- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index ff5961202..9c94e40d9 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -618,7 +618,7 @@ class Installer } if ($this->executeOperations || $this->writeLock) { - $localRepo->write($this->devMode); + $localRepo->write($this->devMode, $this->installationManager); } $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType); @@ -630,7 +630,7 @@ class Installer if ($this->executeOperations) { // force source/dist urls to be updated for all packages $this->processPackageUrls($pool, $policy, $localRepo, $repositories); - $localRepo->write($this->devMode); + $localRepo->write($this->devMode, $this->installationManager); } return array(0, $devPackages); diff --git a/src/Composer/Repository/FilesystemRepository.php b/src/Composer/Repository/FilesystemRepository.php index f0876dfcc..0b6563dd6 100644 --- a/src/Composer/Repository/FilesystemRepository.php +++ b/src/Composer/Repository/FilesystemRepository.php @@ -15,6 +15,8 @@ namespace Composer\Repository; use Composer\Json\JsonFile; use Composer\Package\Loader\ArrayLoader; use Composer\Package\Dumper\ArrayDumper; +use Composer\Installer\InstallationManager; +use Composer\Util\Filesystem; /** * Filesystem repository. @@ -84,13 +86,18 @@ class FilesystemRepository extends WritableArrayRepository /** * Writes writable repository. */ - public function write($devMode) + public function write($devMode, InstallationManager $installationManager) { $data = array('packages' => array(), 'dev' => $devMode); $dumper = new ArrayDumper(); + $fs = new Filesystem(); + $repoDir = dirname($fs->normalizePath($this->file->getPath())); foreach ($this->getCanonicalPackages() as $package) { - $data['packages'][] = $dumper->dump($package); + $pkgArray = $dumper->dump($package); + $path = $installationManager->getInstallPath($package); + $pkgArray['install-path'] = ('' !== $path && null !== $path) ? $fs->findShortestPath($repoDir, $path, true) : null; + $data['packages'][] = $pkgArray; } usort($data['packages'], function ($a, $b) { diff --git a/src/Composer/Repository/WritableArrayRepository.php b/src/Composer/Repository/WritableArrayRepository.php index 284f9bcb0..3580593bb 100644 --- a/src/Composer/Repository/WritableArrayRepository.php +++ b/src/Composer/Repository/WritableArrayRepository.php @@ -13,6 +13,7 @@ namespace Composer\Repository; use Composer\Package\AliasPackage; +use Composer\Installer\InstallationManager; /** * Writable array repository. @@ -24,7 +25,7 @@ class WritableArrayRepository extends ArrayRepository implements WritableReposit /** * {@inheritDoc} */ - public function write($devMode) + public function write($devMode, InstallationManager $installationManager) { } diff --git a/src/Composer/Repository/WritableRepositoryInterface.php b/src/Composer/Repository/WritableRepositoryInterface.php index 4fb3d0c66..c35fdb257 100644 --- a/src/Composer/Repository/WritableRepositoryInterface.php +++ b/src/Composer/Repository/WritableRepositoryInterface.php @@ -13,6 +13,7 @@ namespace Composer\Repository; use Composer\Package\PackageInterface; +use Composer\Installer\InstallationManager; /** * Writable repository interface. @@ -26,7 +27,7 @@ interface WritableRepositoryInterface extends RepositoryInterface * * @param bool $devMode Whether dev requirements were included or not in this installation */ - public function write($devMode); + public function write($devMode, InstallationManager $installationManager); /** * Adds package to the repository. diff --git a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php index 8c8c280e8..574cfbd83 100644 --- a/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php +++ b/tests/Composer/Test/Mock/InstalledFilesystemRepositoryMock.php @@ -13,6 +13,7 @@ namespace Composer\Test\Mock; use Composer\Repository\InstalledFilesystemRepository; +use Composer\Installer\InstallationManager; class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository { @@ -20,7 +21,7 @@ class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository { } - public function write($devMode) + public function write($devMode, InstallationManager $installationManager) { } } diff --git a/tests/Composer/Test/Package/LockerTest.php b/tests/Composer/Test/Package/LockerTest.php index 6b945bab2..bb8eaabde 100644 --- a/tests/Composer/Test/Package/LockerTest.php +++ b/tests/Composer/Test/Package/LockerTest.php @@ -24,7 +24,7 @@ class LockerTest extends TestCase $locker = new Locker( new NullIO, $json, - $this->createRepositoryManagerMock(), + $this->createInstallationManagerMock(), $this->getJsonContent() ); @@ -259,7 +259,7 @@ class LockerTest extends TestCase ->disableOriginalConstructor() ->getMock(); } - + private function createInstallationManagerMock() { $mock = $this->getMockBuilder('Composer\Installer\InstallationManager') diff --git a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php index 4d8d7c103..97747ebc5 100644 --- a/tests/Composer/Test/Repository/FilesystemRepositoryTest.php +++ b/tests/Composer/Test/Repository/FilesystemRepositoryTest.php @@ -82,11 +82,21 @@ class FilesystemRepositoryTest extends TestCase $json = $this->createJsonFileMock(); $repository = new FilesystemRepository($json); + $im = $this->getMockBuilder('Composer\Installer\InstallationManager') + ->disableOriginalConstructor() + ->getMock(); + $im->expects($this->once()) + ->method('getInstallPath') + ->will($this->returnValue('/foo/bar/vendor/woop/woop')); $json ->expects($this->once()) ->method('read') ->will($this->returnValue(array())); + $json + ->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/foo/bar/vendor/composer/installed.json')); $json ->expects($this->once()) ->method('exists') @@ -95,12 +105,12 @@ class FilesystemRepositoryTest extends TestCase ->expects($this->once()) ->method('write') ->with(array( - 'packages' => array(array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0')), + 'packages' => array(array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0', 'install-path' => '../woop/woop')), 'dev' => true, )); $repository->addPackage($this->getPackage('mypkg', '0.1.10')); - $repository->write(true); + $repository->write(true, $im); } private function createJsonFileMock() From bfee701f9b601ce0fadfd9982d1b18cfd9728019 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 21:54:41 +0200 Subject: [PATCH 45/48] Deduplicate findHeaderValue code --- src/Composer/Repository/Vcs/GitHubDriver.php | 5 ++- src/Composer/Util/Http/CurlDownloader.php | 1 - src/Composer/Util/Http/Response.php | 38 ++++++++++++-------- src/Composer/Util/RemoteFilesystem.php | 34 ++++-------------- 4 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index b5c3fb14a..55940e212 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -19,7 +19,6 @@ use Composer\Cache; use Composer\IO\IOInterface; use Composer\Util\GitHub; use Composer\Util\Http\Response; -use Composer\Util\RemoteFilesystem; /** * @author Jordi Boggiano @@ -346,10 +345,10 @@ class GitHubDriver extends VcsDriver $scopesIssued = array(); $scopesNeeded = array(); if ($headers = $e->getHeaders()) { - if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-OAuth-Scopes')) { + if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) { $scopesIssued = explode(' ', $scopes); } - if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { + if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) { $scopesNeeded = explode(' ', $scopes); } } diff --git a/src/Composer/Util/Http/CurlDownloader.php b/src/Composer/Util/Http/CurlDownloader.php index f7ae28a24..a163290fe 100644 --- a/src/Composer/Util/Http/CurlDownloader.php +++ b/src/Composer/Util/Http/CurlDownloader.php @@ -16,7 +16,6 @@ use Composer\Config; use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; -use Composer\Util\RemoteFilesystem; use Composer\Util\StreamContextFactory; use Composer\Util\AuthHelper; use Composer\Util\Url; diff --git a/src/Composer/Util/Http/Response.php b/src/Composer/Util/Http/Response.php index d2774c938..1b4581331 100644 --- a/src/Composer/Util/Http/Response.php +++ b/src/Composer/Util/Http/Response.php @@ -61,20 +61,7 @@ class Response public function getHeader($name) { - $value = null; - foreach ($this->headers as $header) { - if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) { - $value = $match[1]; - } elseif (preg_match('{^HTTP/}i', $header)) { - // TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary - // - // In case of redirects, headers contains the headers of all responses - // so we reset the flag when a new response is being parsed as we are only interested in the last response - $value = null; - } - } - - return $value; + return self::findHeaderValue($this->headers, $name); } public function getBody() @@ -91,4 +78,27 @@ class Response { $this->request = $this->code = $this->headers = $this->body = null; } + + /** + * @param array $headers array of returned headers like from getLastHeaders() + * @param string $name header name (case insensitive) + * @return string|null + */ + public static function findHeaderValue(array $headers, $name) + { + $value = null; + foreach ($headers as $header) { + if (preg_match('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) { + $value = $match[1]; + } elseif (preg_match('{^HTTP/}i', $header)) { + // TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary + // + // In case of redirects, http_response_headers contains the headers of all responses + // so we reset the flag when a new response is being parsed as we are only interested in the last response + $value = null; + } + } + + return $value; + } } diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index d9803835d..48711815a 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -17,6 +17,7 @@ use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Composer\Util\HttpDownloader; +use Composer\Util\Response; /** * @author François Pluchino @@ -143,27 +144,6 @@ class RemoteFilesystem return $this->lastHeaders; } - /** - * @param array $headers array of returned headers like from getLastHeaders() - * @param string $name header name (case insensitive) - * @return string|null - */ - public static function findHeaderValue(array $headers, $name) - { - $value = null; - foreach ($headers as $header) { - if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) { - $value = $match[1]; - } elseif (preg_match('{^HTTP/}i', $header)) { - // In case of redirects, http_response_headers contains the headers of all responses - // so we reset the flag when a new response is being parsed as we are only interested in the last response - $value = null; - } - } - - return $value; - } - /** * @param array $headers array of returned headers like from getLastHeaders() * @return int|null @@ -294,7 +274,7 @@ class RemoteFilesystem if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); - if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') { + if ($statusCode >= 400 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') { HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true)); } @@ -303,7 +283,7 @@ class RemoteFilesystem } } - $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null; + $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength); @@ -360,8 +340,8 @@ class RemoteFilesystem $locationHeader = null; if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); - $contentType = $this->findHeaderValue($http_response_header, 'content-type'); - $locationHeader = $this->findHeaderValue($http_response_header, 'location'); + $contentType = Response::findHeaderValue($http_response_header, 'content-type'); + $locationHeader = Response::findHeaderValue($http_response_header, 'location'); } // check for bitbucket login page asking to authenticate @@ -417,7 +397,7 @@ class RemoteFilesystem // decode gzip if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) { - $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding'); + $contentEncoding = Response::findHeaderValue($http_response_header, 'content-encoding'); $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding); if ($decode) { @@ -700,7 +680,7 @@ class RemoteFilesystem private function handleRedirect(array $http_response_header, array $additionalOptions, $result) { - if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) { + if ($locationHeader = Response::findHeaderValue($http_response_header, 'location')) { if (parse_url($locationHeader, PHP_URL_SCHEME)) { // Absolute URL; e.g. https://example.com/composer $targetUrl = $locationHeader; From 4dabc17ec1e55d42d9611271d1728dcf9f79d56a Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 2 Aug 2019 22:21:52 +0200 Subject: [PATCH 46/48] Fix use statement --- src/Composer/Util/RemoteFilesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 48711815a..76a2176a8 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -17,7 +17,7 @@ use Composer\IO\IOInterface; use Composer\Downloader\TransportException; use Composer\CaBundle\CaBundle; use Composer\Util\HttpDownloader; -use Composer\Util\Response; +use Composer\Util\Http\Response; /** * @author François Pluchino From 53d2ab2253c5f73579ea4b845bfd99f28c15a6cf Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Aug 2019 11:37:23 +0200 Subject: [PATCH 47/48] Split up steps on VCS downloaders to allow doing network operations before touching the filesystem on GitDownloader, fixes #7903 --- src/Composer/Downloader/DownloadManager.php | 72 ++++-- .../Downloader/DownloaderInterface.php | 37 ++- src/Composer/Downloader/FileDownloader.php | 18 +- src/Composer/Downloader/FossilDownloader.php | 12 +- src/Composer/Downloader/GitDownloader.php | 111 ++++++--- src/Composer/Downloader/HgDownloader.php | 12 +- src/Composer/Downloader/PathDownloader.php | 2 +- .../Downloader/PerforceDownloader.php | 10 +- src/Composer/Downloader/SvnDownloader.php | 12 +- src/Composer/Downloader/VcsDownloader.php | 132 +++++++--- src/Composer/Downloader/ZipDownloader.php | 4 +- .../Installer/InstallationManager.php | 58 ++++- src/Composer/Installer/InstallerInterface.php | 50 +++- src/Composer/Installer/LibraryInstaller.php | 25 ++ .../Installer/MetapackageInstaller.php | 16 ++ src/Composer/Installer/NoopInstaller.php | 14 ++ src/Composer/Installer/ProjectInstaller.php | 16 ++ src/Composer/Util/Git.php | 8 +- .../Test/Downloader/FossilDownloaderTest.php | 8 +- .../Test/Downloader/GitDownloaderTest.php | 235 +++++++++--------- .../Test/Downloader/HgDownloaderTest.php | 6 + .../Test/Downloader/ZipDownloaderTest.php | 2 +- 22 files changed, 622 insertions(+), 238 deletions(-) diff --git a/src/Composer/Downloader/DownloadManager.php b/src/Composer/Downloader/DownloadManager.php index a23c167b5..794998bb2 100644 --- a/src/Composer/Downloader/DownloadManager.php +++ b/src/Composer/Downloader/DownloadManager.php @@ -165,9 +165,9 @@ class DownloadManager /** * Downloads package into target dir. * - * @param PackageInterface $package package instance - * @param string $targetDir target dir - * @param PackageInterface $prevPackage previous package instance in case of updates + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates * * @return PromiseInterface * @throws \InvalidArgumentException if package have no urls to download from @@ -182,7 +182,7 @@ class DownloadManager $io = $this->io; $self = $this; - $download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) { + $download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download, $prevPackage) { $source = array_shift($sources); if ($retry) { $io->writeError(' Now trying to download from ' . $source . ''); @@ -214,7 +214,7 @@ class DownloadManager }; try { - $result = $downloader->download($package, $targetDir); + $result = $downloader->download($package, $targetDir, $prevPackage); } catch (\Exception $e) { return $handleError($e); } @@ -232,12 +232,31 @@ class DownloadManager return $download(); } + /** + * Prepares an operation execution + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * + * @return PromiseInterface|null + */ + public function prepare($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null) + { + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->prepare($type, $package, $targetDir, $prevPackage); + } + } + /** * Installs package into target dir. * * @param PackageInterface $package package instance * @param string $targetDir target dir * + * @return PromiseInterface|null * @throws \InvalidArgumentException if package have no urls to download from * @throws \RuntimeException */ @@ -245,7 +264,7 @@ class DownloadManager { $downloader = $this->getDownloaderForPackage($package); if ($downloader) { - $downloader->install($package, $targetDir); + return $downloader->install($package, $targetDir); } } @@ -256,6 +275,7 @@ class DownloadManager * @param PackageInterface $target target package version * @param string $targetDir target dir * + * @return PromiseInterface|null * @throws \InvalidArgumentException if initial package is not installed */ public function update(PackageInterface $initial, PackageInterface $target, $targetDir) @@ -270,17 +290,14 @@ class DownloadManager // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed if (!$downloader) { - $initialDownloader->remove($initial, $targetDir); - return; + return $initialDownloader->remove($initial, $targetDir); } $initialType = $this->getDownloaderType($initialDownloader); $targetType = $this->getDownloaderType($downloader); if ($initialType === $targetType) { try { - $downloader->update($initial, $target, $targetDir); - - return; + return $downloader->update($initial, $target, $targetDir); } catch (\RuntimeException $e) { if (!$this->io->isInteractive()) { throw $e; @@ -294,8 +311,15 @@ class DownloadManager // if downloader type changed, or update failed and user asks for reinstall, // we wipe the dir and do a new install instead of updating it - $initialDownloader->remove($initial, $targetDir); - $this->install($target, $targetDir); + $promise = $initialDownloader->remove($initial, $targetDir); + if ($promise) { + $self = $this; + return $promise->then(function ($res) use ($self, $target, $targetDir) { + return $self->install($target, $targetDir); + }); + } + + return $this->install($target, $targetDir); } /** @@ -303,12 +327,32 @@ class DownloadManager * * @param PackageInterface $package package instance * @param string $targetDir target dir + * + * @return PromiseInterface|null */ public function remove(PackageInterface $package, $targetDir) { $downloader = $this->getDownloaderForPackage($package); if ($downloader) { - $downloader->remove($package, $targetDir); + return $downloader->remove($package, $targetDir); + } + } + + /** + * Cleans up a failed operation + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $targetDir target dir + * @param PackageInterface|null $prevPackage previous package instance in case of updates + * + * @return PromiseInterface|null + */ + public function cleanup($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null) + { + $downloader = $this->getDownloaderForPackage($package); + if ($downloader) { + return $downloader->cleanup($type, $package, $targetDir, $prevPackage); } } diff --git a/src/Composer/Downloader/DownloaderInterface.php b/src/Composer/Downloader/DownloaderInterface.php index 2074b16da..01e7f95c8 100644 --- a/src/Composer/Downloader/DownloaderInterface.php +++ b/src/Composer/Downloader/DownloaderInterface.php @@ -31,14 +31,30 @@ interface DownloaderInterface public function getInstallationSource(); /** - * This should do any network-related tasks to prepare for install/update + * This should do any network-related tasks to prepare for an upcoming install/update * * @return PromiseInterface|null */ - public function download(PackageInterface $package, $path); + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null); /** - * Downloads specific package into specific folder. + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + */ + public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null); + + /** + * Installs specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path @@ -61,4 +77,19 @@ interface DownloaderInterface * @param string $path download path */ public function remove(PackageInterface $package, $path); + + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give + * all installers a change to cleanup things they did previously, so you need to keep track of changes + * applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param string $path download path + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + */ + public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null); } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index ab72bbaed..20d21804d 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -84,7 +84,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface /** * {@inheritDoc} */ - public function download(PackageInterface $package, $path, $output = true) + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true) { if (!$package->getDistUrl()) { throw new \InvalidArgumentException('The given package is missing url information'); @@ -222,6 +222,20 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface return $download(); } + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null) + { + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null) + { + } + /** * {@inheritDoc} */ @@ -336,7 +350,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface $e = null; try { - $res = $this->download($package, $targetDir.'_compare', false); + $res = $this->download($package, $targetDir.'_compare', null, false); $this->httpDownloader->wait(); $res = $this->install($package, $targetDir.'_compare', false); diff --git a/src/Composer/Downloader/FossilDownloader.php b/src/Composer/Downloader/FossilDownloader.php index a814f89b7..be7c987b3 100644 --- a/src/Composer/Downloader/FossilDownloader.php +++ b/src/Composer/Downloader/FossilDownloader.php @@ -23,7 +23,15 @@ class FossilDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doInstall(PackageInterface $package, $path, $url) + protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) + { + + } + + /** + * {@inheritDoc} + */ + protected function doInstall(PackageInterface $package, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); @@ -49,7 +57,7 @@ class FossilDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) + protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { // Ensure we are allowed to use this URL by config $this->config->prohibitUrlByConfig($url, $this->io); diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index f698981fe..300749ca8 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -29,6 +29,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface private $hasStashedChanges = false; private $hasDiscardedChanges = false; private $gitUtil; + private $cachedPackages = array(); public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null) { @@ -39,7 +40,28 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface /** * {@inheritDoc} */ - public function doInstall(PackageInterface $package, $path, $url) + protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) + { + GitUtil::cleanEnv(); + + $cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/'; + $gitVersion = $this->gitUtil->getVersion(); + + // --dissociate option is only available since git 2.3.0-rc0 + if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { + $this->io->writeError(" - Syncing " . $package->getName() . " (" . $package->getFullPrettyVersion() . ") into cache"); + $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); + $ref = $package->getSourceReference(); + if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref) && is_dir($cachePath)) { + $this->cachedPackages[$package->getId()][$ref] = true; + } + } + } + + /** + * {@inheritDoc} + */ + protected function doInstall(PackageInterface $package, $path, $url) { GitUtil::cleanEnv(); $path = $this->normalizePath($path); @@ -47,26 +69,20 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface $ref = $package->getSourceReference(); $flag = Platform::isWindows() ? '/D ' : ''; - // --dissociate option is only available since git 2.3.0-rc0 - $gitVersion = $this->gitUtil->getVersion(); - $msg = "Cloning ".$this->getShortHash($ref); - - $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer'; - if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) { - $this->io->writeError('', true, IOInterface::DEBUG); - $this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG); - try { - $this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref); - if (is_dir($cachePath)) { - $command = - 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% ' - . '&& cd '.$flag.'%path% ' - . '&& git remote set-url origin %url% && git remote add composer %url%'; - $msg = "Cloning ".$this->getShortHash($ref).' from cache'; - } - } catch (\RuntimeException $e) { + if (!empty($this->cachedPackages[$package->getId()][$ref])) { + $msg = "Cloning ".$this->getShortHash($ref).' from cache'; + $command = + 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% ' + . '&& cd '.$flag.'%path% ' + . '&& git remote set-url origin %url% && git remote add composer %url%'; + } else { + $msg = "Cloning ".$this->getShortHash($ref); + $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer'; + if (getenv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); } } + $this->io->writeError($msg); $commandCallable = function ($url) use ($path, $command, $cachePath) { @@ -99,13 +115,51 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface /** * {@inheritDoc} */ - public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) + protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { GitUtil::cleanEnv(); + $path = $this->normalizePath($path); if (!$this->hasMetadataRepository($path)) { throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information'); } + $cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/'; + $ref = $target->getSourceReference(); + $flag = Platform::isWindows() ? '/D ' : ''; + + if (!empty($this->cachedPackages[$target->getId()][$ref])) { + $msg = "Checking out ".$this->getShortHash($ref).' from cache'; + $command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %url%'; + } else { + $msg = "Checking out ".$this->getShortHash($ref); + $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)'; + if (getenv('COMPOSER_DISABLE_NETWORK')) { + throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); + } + } + + $this->io->writeError($msg); + + $commandCallable = function ($url) use ($ref, $command, $cachePath) { + return str_replace( + array('%url%', '%ref%', '%cachePath%'), + array( + ProcessExecutor::escape($url), + ProcessExecutor::escape($ref.'^{commit}'), + ProcessExecutor::escape($cachePath), + ), + $command + ); + }; + + $this->gitUtil->runCommand($commandCallable, $url, $path); + if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) { + if ($target->getDistReference() === $target->getSourceReference()) { + $target->setDistReference($newRef); + } + $target->setSourceReference($newRef); + } + $updateOriginUrl = false; if ( 0 === $this->process->execute('git remote -v', $output, $path) @@ -116,23 +170,6 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface $updateOriginUrl = true; } } - - $ref = $target->getSourceReference(); - $this->io->writeError(" Checking out ".$this->getShortHash($ref)); - $command = 'git remote set-url composer %s && git rev-parse --quiet --verify %s || (git fetch composer && git fetch --tags composer)'; - - $commandCallable = function ($url) use ($command, $ref) { - return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($ref.'^{commit}')); - }; - - $this->gitUtil->runCommand($commandCallable, $url, $path); - if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) { - if ($target->getDistReference() === $target->getSourceReference()) { - $target->setDistReference($newRef); - } - $target->setSourceReference($newRef); - } - if ($updateOriginUrl) { $this->updateOriginUrl($path, $target->getSourceUrl()); } diff --git a/src/Composer/Downloader/HgDownloader.php b/src/Composer/Downloader/HgDownloader.php index add381a75..91144a13d 100644 --- a/src/Composer/Downloader/HgDownloader.php +++ b/src/Composer/Downloader/HgDownloader.php @@ -24,7 +24,15 @@ class HgDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doInstall(PackageInterface $package, $path, $url) + protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) + { + + } + + /** + * {@inheritDoc} + */ + protected function doInstall(PackageInterface $package, $path, $url) { $hgUtils = new HgUtils($this->io, $this->config, $this->process); @@ -44,7 +52,7 @@ class HgDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) + protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { $hgUtils = new HgUtils($this->io, $this->config, $this->process); diff --git a/src/Composer/Downloader/PathDownloader.php b/src/Composer/Downloader/PathDownloader.php index e289c0173..b84396416 100644 --- a/src/Composer/Downloader/PathDownloader.php +++ b/src/Composer/Downloader/PathDownloader.php @@ -37,7 +37,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter /** * {@inheritdoc} */ - public function download(PackageInterface $package, $path, $output = true) + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true) { $url = $package->getDistUrl(); $realUrl = realpath($url); diff --git a/src/Composer/Downloader/PerforceDownloader.php b/src/Composer/Downloader/PerforceDownloader.php index 777866714..8be866929 100644 --- a/src/Composer/Downloader/PerforceDownloader.php +++ b/src/Composer/Downloader/PerforceDownloader.php @@ -24,6 +24,14 @@ class PerforceDownloader extends VcsDownloader /** @var Perforce */ protected $perforce; + /** + * {@inheritDoc} + */ + protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) + { + + } + /** * {@inheritDoc} */ @@ -76,7 +84,7 @@ class PerforceDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) + protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { $this->doInstall($target, $path, $url); } diff --git a/src/Composer/Downloader/SvnDownloader.php b/src/Composer/Downloader/SvnDownloader.php index 0aae163c6..35f01eb68 100644 --- a/src/Composer/Downloader/SvnDownloader.php +++ b/src/Composer/Downloader/SvnDownloader.php @@ -28,7 +28,15 @@ class SvnDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doInstall(PackageInterface $package, $path, $url) + protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) + { + + } + + /** + * {@inheritDoc} + */ + protected function doInstall(PackageInterface $package, $path, $url) { SvnUtil::cleanEnv(); $ref = $package->getSourceReference(); @@ -48,7 +56,7 @@ class SvnDownloader extends VcsDownloader /** * {@inheritDoc} */ - public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) + protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url) { SvnUtil::cleanEnv(); $ref = $target->getSourceReference(); diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index b87f6433a..15bf55072 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -54,9 +54,57 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa /** * {@inheritDoc} */ - public function download(PackageInterface $package, $path) + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null) { - // noop for now, ideally we would do a git fetch already here, or make sure the cached git repo is synced, etc. + if (!$package->getSourceReference()) { + throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information'); + } + + $urls = $this->prepareUrls($package->getSourceUrls()); + + while ($url = array_shift($urls)) { + try { + return $this->doDownload($package, $path, $url, $prevPackage); + } catch (\Exception $e) { + // rethrow phpunit exceptions to avoid hard to debug bug failures + if ($e instanceof \PHPUnit_Framework_Exception) { + throw $e; + } + if ($this->io->isDebug()) { + $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage()); + } elseif (count($urls)) { + $this->io->writeError(' Failed, trying the next URL'); + } + if (!count($urls)) { + throw $e; + } + } + } + } + + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null) + { + if ($type === 'update') { + $this->cleanChanges($prevPackage, $path, true); + } elseif ($type === 'install') { + $this->filesystem->emptyDirectory($path); + } elseif ($type === 'uninstall') { + $this->cleanChanges($package, $path, false); + } + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null) + { + if ($type === 'update') { + // TODO keep track of whether prepare was called for this package + $this->reapplyChanges($path); + } } /** @@ -69,32 +117,10 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } $this->io->writeError(" - Installing " . $package->getName() . " (" . $package->getFullPrettyVersion() . "): ", false); - $this->filesystem->emptyDirectory($path); - $urls = $package->getSourceUrls(); + $urls = $this->prepareUrls($package->getSourceUrls()); while ($url = array_shift($urls)) { try { - if (Filesystem::isLocalPath($url)) { - // realpath() below will not understand - // url that starts with "file://" - $needle = 'file://'; - $isFileProtocol = false; - if (0 === strpos($url, $needle)) { - $url = substr($url, strlen($needle)); - $isFileProtocol = true; - } - - // realpath() below will not understand %20 spaces etc. - if (false !== strpos($url, '%')) { - $url = rawurldecode($url); - } - - $url = realpath($url); - - if ($isFileProtocol) { - $url = $needle . $url; - } - } $this->doInstall($package, $path, $url); break; } catch (\Exception $e) { @@ -141,15 +167,11 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading'; $this->io->writeError(" - " . $actionName . " " . $name . " (" . $from . " => " . $to . "): ", false); - $this->cleanChanges($initial, $path, true); - $urls = $target->getSourceUrls(); + $urls = $this->prepareUrls($target->getSourceUrls()); $exception = null; while ($url = array_shift($urls)) { try { - if (Filesystem::isLocalPath($url)) { - $url = realpath($url); - } $this->doUpdate($initial, $target, $path, $url); $exception = null; @@ -167,8 +189,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } } - $this->reapplyChanges($path); - // print the commit logs if in verbose mode and VCS metadata is present // because in case of missing metadata code would trigger another exception if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) { @@ -204,7 +224,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa public function remove(PackageInterface $package, $path) { $this->io->writeError(" - Removing " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); - $this->cleanChanges($package, $path, false); if (!$this->filesystem->removeDirectory($path)) { throw new \RuntimeException('Could not completely delete '.$path.', aborting.'); } @@ -243,7 +262,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa } /** - * Guarantee that no changes have been made to the local copy + * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not) * * @param string $path * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly @@ -252,12 +271,26 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa { } + /** + * Downloads data needed to run an install/update later + * + * @param PackageInterface $package package instance + * @param string $path download path + * @param string $url package url + * @param PackageInterface|null $prevPackage previous package (in case of an update) + * + * @return PromiseInterface|null + */ + abstract protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null); + /** * Downloads specific package into specific folder. * * @param PackageInterface $package package instance * @param string $path download path * @param string $url package url + * + * @return PromiseInterface|null */ abstract protected function doInstall(PackageInterface $package, $path, $url); @@ -268,6 +301,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa * @param PackageInterface $target updated package * @param string $path download path * @param string $url package url + * + * @return PromiseInterface|null */ abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url); @@ -289,4 +324,33 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa * @return bool */ abstract protected function hasMetadataRepository($path); + + private function prepareUrls(array $urls) + { + foreach ($urls as $index => $url) { + if (Filesystem::isLocalPath($url)) { + // realpath() below will not understand + // url that starts with "file://" + $fileProtocol = 'file://'; + $isFileProtocol = false; + if (0 === strpos($url, $fileProtocol)) { + $url = substr($url, strlen($fileProtocol)); + $isFileProtocol = true; + } + + // realpath() below will not understand %20 spaces etc. + if (false !== strpos($url, '%')) { + $url = rawurldecode($url); + } + + $urls[$index] = realpath($url); + + if ($isFileProtocol) { + $urls[$index] = $fileProtocol . $urls[$index]; + } + } + } + + return $urls; + } } diff --git a/src/Composer/Downloader/ZipDownloader.php b/src/Composer/Downloader/ZipDownloader.php index bd8d3b499..160bae1d6 100644 --- a/src/Composer/Downloader/ZipDownloader.php +++ b/src/Composer/Downloader/ZipDownloader.php @@ -47,7 +47,7 @@ class ZipDownloader extends ArchiveDownloader /** * {@inheritDoc} */ - public function download(PackageInterface $package, $path, $output = true) + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true) { if (null === self::$hasSystemUnzip) { $finder = new ExecutableFinder; @@ -76,7 +76,7 @@ class ZipDownloader extends ArchiveDownloader } } - return parent::download($package, $path, $output); + return parent::download($package, $path, $prevPackage, $output); } /** diff --git a/src/Composer/Installer/InstallationManager.php b/src/Composer/Installer/InstallationManager.php index ce10dc4da..f018c0a31 100644 --- a/src/Composer/Installer/InstallationManager.php +++ b/src/Composer/Installer/InstallationManager.php @@ -177,11 +177,52 @@ class InstallationManager $promise = $installer->download($target, $operation->getInitialPackage()); } - if (isset($promise)) { + if (!empty($promise)) { $this->loop->wait(array($promise)); } - $this->$method($repo, $operation); + $e = null; + try { + if ($method === 'install' || $method === 'uninstall') { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + $promise = $installer->prepare($method, $package); + } elseif ($method === 'update') { + $target = $operation->getTargetPackage(); + $targetType = $target->getType(); + $installer = $this->getInstaller($targetType); + $promise = $installer->prepare('update', $target, $operation->getInitialPackage()); + } + + if (!empty($promise)) { + $this->loop->wait(array($promise)); + } + + $promise = $this->$method($repo, $operation); + if (!empty($promise)) { + $this->loop->wait(array($promise)); + } + } catch (\Exception $e) { + } + + if ($method === 'install' || $method === 'uninstall') { + $package = $operation->getPackage(); + $installer = $this->getInstaller($package->getType()); + $promise = $installer->cleanup($method, $package); + } elseif ($method === 'update') { + $target = $operation->getTargetPackage(); + $targetType = $target->getType(); + $installer = $this->getInstaller($targetType); + $promise = $installer->cleanup('update', $target, $operation->getInitialPackage()); + } + + if (!empty($promise)) { + $this->loop->wait(array($promise)); + } + + if ($e) { + throw $e; + } } /** @@ -194,8 +235,10 @@ class InstallationManager { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); - $installer->install($repo, $package); + $promise = $installer->install($repo, $package); $this->markForNotification($package); + + return $promise; } /** @@ -214,13 +257,15 @@ class InstallationManager if ($initialType === $targetType) { $installer = $this->getInstaller($initialType); - $installer->update($repo, $initial, $target); + $promise = $installer->update($repo, $initial, $target); $this->markForNotification($target); } else { $this->getInstaller($initialType)->uninstall($repo, $initial); $installer = $this->getInstaller($targetType); - $installer->install($repo, $target); + $promise = $installer->install($repo, $target); } + + return $promise; } /** @@ -233,7 +278,8 @@ class InstallationManager { $package = $operation->getPackage(); $installer = $this->getInstaller($package->getType()); - $installer->uninstall($repo, $package); + + return $installer->uninstall($repo, $package); } /** diff --git a/src/Composer/Installer/InstallerInterface.php b/src/Composer/Installer/InstallerInterface.php index 310c5fcfc..cc4bef7e9 100644 --- a/src/Composer/Installer/InstallerInterface.php +++ b/src/Composer/Installer/InstallerInterface.php @@ -46,26 +46,43 @@ interface InstallerInterface /** * Downloads the files needed to later install the given package. * - * @param PackageInterface $package package instance - * @param PackageInterface $prevPackage previous package instance in case of an update + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update * @return PromiseInterface|null */ public function download(PackageInterface $package, PackageInterface $prevPackage = null); + /** + * Do anything that needs to be done between all downloads have been completed and the actual operation is executed + * + * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore + * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or + * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can + * be undone as much as possible. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + */ + public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null); + /** * Installs specific package. * - * @param InstalledRepositoryInterface $repo repository in which to check - * @param PackageInterface $package package instance + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null */ public function install(InstalledRepositoryInterface $repo, PackageInterface $package); /** * Updates specific package. * - * @param InstalledRepositoryInterface $repo repository in which to check - * @param PackageInterface $initial already installed package version - * @param PackageInterface $target updated version + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $initial already installed package version + * @param PackageInterface $target updated version + * @return PromiseInterface|null * * @throws InvalidArgumentException if $initial package is not installed */ @@ -74,11 +91,26 @@ interface InstallerInterface /** * Uninstalls specific package. * - * @param InstalledRepositoryInterface $repo repository in which to check - * @param PackageInterface $package package instance + * @param InstalledRepositoryInterface $repo repository in which to check + * @param PackageInterface $package package instance + * @return PromiseInterface|null */ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package); + /** + * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps + * + * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give + * all installers a change to cleanup things they did previously, so you need to keep track of changes + * applied in the installer/downloader themselves. + * + * @param string $type one of install/update/uninstall + * @param PackageInterface $package package instance + * @param PackageInterface $prevPackage previous package instance in case of an update + * @return PromiseInterface|null + */ + public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null); + /** * Returns the installation path of a package * diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index a89553b1b..5e99e1f47 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/src/Composer/Installer/LibraryInstaller.php @@ -85,6 +85,9 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath); } + /** + * {@inheritDoc} + */ public function download(PackageInterface $package, PackageInterface $prevPackage = null) { $this->initializeVendorDir(); @@ -93,6 +96,28 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface return $this->downloadManager->download($package, $downloadPath, $prevPackage); } + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->downloadManager->prepare($type, $package, $downloadPath, $prevPackage); + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + $this->initializeVendorDir(); + $downloadPath = $this->getInstallPath($package); + + return $this->downloadManager->cleanup($type, $package, $downloadPath, $prevPackage); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/MetapackageInstaller.php b/src/Composer/Installer/MetapackageInstaller.php index b47c00740..1ed6beb71 100644 --- a/src/Composer/Installer/MetapackageInstaller.php +++ b/src/Composer/Installer/MetapackageInstaller.php @@ -55,6 +55,22 @@ class MetapackageInstaller implements InstallerInterface // noop } + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + // noop + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + // noop + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/NoopInstaller.php b/src/Composer/Installer/NoopInstaller.php index 51df3c305..4fe581ff5 100644 --- a/src/Composer/Installer/NoopInstaller.php +++ b/src/Composer/Installer/NoopInstaller.php @@ -47,6 +47,20 @@ class NoopInstaller implements InstallerInterface { } + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Installer/ProjectInstaller.php b/src/Composer/Installer/ProjectInstaller.php index 350b220f5..069c741ec 100644 --- a/src/Composer/Installer/ProjectInstaller.php +++ b/src/Composer/Installer/ProjectInstaller.php @@ -71,6 +71,22 @@ class ProjectInstaller implements InstallerInterface return $this->downloadManager->download($package, $installPath, $prevPackage); } + /** + * {@inheritDoc} + */ + public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage); + } + + /** + * {@inheritDoc} + */ + public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null) + { + $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage); + } + /** * {@inheritDoc} */ diff --git a/src/Composer/Util/Git.php b/src/Composer/Util/Git.php index 74e5c286f..48e91ba84 100644 --- a/src/Composer/Util/Git.php +++ b/src/Composer/Util/Git.php @@ -224,6 +224,10 @@ class Git public function syncMirror($url, $dir) { + if (getenv('COMPOSER_DISABLE_NETWORK')) { + return false; + } + // update the repo if it is a valid git repository if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { try { @@ -260,9 +264,7 @@ class Git } } - $this->syncMirror($url, $dir); - - return false; + return $this->syncMirror($url, $dir); } private function isAuthenticationFailure($url, &$match) diff --git a/tests/Composer/Test/Downloader/FossilDownloaderTest.php b/tests/Composer/Test/Downloader/FossilDownloaderTest.php index 9ab7b6b84..4ec8fed45 100644 --- a/tests/Composer/Test/Downloader/FossilDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FossilDownloaderTest.php @@ -48,7 +48,7 @@ class FossilDownloaderTest extends TestCase /** * @expectedException \InvalidArgumentException */ - public function testDownloadForPackageWithoutSourceReference() + public function testInstallForPackageWithoutSourceReference() { $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->once()) @@ -59,7 +59,7 @@ class FossilDownloaderTest extends TestCase $downloader->install($packageMock, '/path'); } - public function testDownload() + public function testInstall() { $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) @@ -104,7 +104,9 @@ class FossilDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); + $downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock); $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + $downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock); } public function testUpdate() @@ -140,7 +142,9 @@ class FossilDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } public function testRemove() diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index b9a85a666..bf1402186 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -17,6 +17,7 @@ use Composer\Config; use Composer\Test\TestCase; use Composer\Util\Filesystem; use Composer\Util\Platform; +use Prophecy\Argument; class GitDownloaderTest extends TestCase { @@ -79,7 +80,10 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); + $downloader->download($packageMock, '/path'); + $downloader->prepare('install', $packageMock, '/path'); $downloader->install($packageMock, '/path'); + $downloader->cleanup('install', $packageMock, '/path'); } public function testDownload() @@ -130,7 +134,10 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); } public function testDownloadWithCache() @@ -195,7 +202,10 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, $config, $processExecutor); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); @rmdir($cachePath); } @@ -265,7 +275,10 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); } public function pushUrlProvider() @@ -329,12 +342,12 @@ class GitDownloaderTest extends TestCase $config->merge(array('config' => array('github-protocols' => $protocols))); $downloader = $this->getDownloaderMock(null, $config, $processExecutor); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); } - /** - * @expectedException \RuntimeException - */ public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() { $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer"); @@ -359,8 +372,20 @@ class GitDownloaderTest extends TestCase ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(1)); - $downloader = $this->getDownloaderMock(null, null, $processExecutor); - $downloader->install($packageMock, 'composerPath'); + // not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe + try { + $downloader = $this->getDownloaderMock(null, null, $processExecutor); + $downloader->download($packageMock, 'composerPath'); + $downloader->prepare('install', $packageMock, 'composerPath'); + $downloader->install($packageMock, 'composerPath'); + $downloader->cleanup('install', $packageMock, 'composerPath'); + $this->fail('This test should throw'); + } catch (\RuntimeException $e) { + if ('RuntimeException' !== get_class($e)) { + throw $e; + } + $this->assertEquals('RuntimeException', get_class($e)); + } } /** @@ -375,7 +400,10 @@ class GitDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); + $downloader->download($sourcePackageMock, '/path', $initialPackageMock); + $downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock); $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + $downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock); } public function testUpdate() @@ -392,39 +420,22 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getVersion') ->will($this->returnValue('1.0.0.0')); - $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $processExecutor->expects($this->at(0)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(1)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(2)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(3)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(4)) - ->method('execute') - ->with($this->equalTo($this->winCompat($expectedGitUpdateCommand)), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(5)) - ->method('execute') - ->with($this->equalTo('git branch -r')) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(6)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) - ->will($this->returnValue(0)); + + $process = $this->prophesize('Composer\Util\ProcessExecutor'); + $process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0); + $process->execute($expectedGitUpdateCommand, null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled(); + $process->execute($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled(); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); - $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); + $downloader = $this->getDownloaderMock(null, new Config(), $process->reveal()); + $downloader->download($packageMock, $this->workingDir, $packageMock); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } public function testUpdateWithNewRepoUrl() @@ -444,27 +455,20 @@ class GitDownloaderTest extends TestCase $packageMock->expects($this->any()) ->method('getVersion') ->will($this->returnValue('1.0.0.0')); + $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); $processExecutor->expects($this->at(0)) ->method('execute') - ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) + ->with($this->equalTo($this->winCompat("git --version"))) ->will($this->returnValue(0)); $processExecutor->expects($this->at(1)) ->method('execute') - ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no"))) + ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) ->will($this->returnValue(0)); $processExecutor->expects($this->at(2)) ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnCallback(function ($cmd, &$output, $cwd) { - $output = 'origin https://github.com/old/url (fetch) -origin https://github.com/old/url (push) -composer https://github.com/old/url (fetch) -composer https://github.com/old/url (push) -'; - - return 0; - })); + ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no"))) + ->will($this->returnValue(0)); $processExecutor->expects($this->at(3)) ->method('execute') ->with($this->equalTo($this->winCompat("git remote -v"))) @@ -482,26 +486,41 @@ composer https://github.com/old/url (push) ->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) ->will($this->returnValue(0)); $processExecutor->expects($this->at(7)) + ->method('execute') + ->with($this->equalTo($this->winCompat("git remote -v"))) + ->will($this->returnCallback(function ($cmd, &$output, $cwd) { + $output = 'origin https://github.com/old/url (fetch) +origin https://github.com/old/url (push) +composer https://github.com/old/url (fetch) +composer https://github.com/old/url (push) +'; + + return 0; + })); + $processExecutor->expects($this->at(8)) ->method('execute') ->with($this->equalTo($this->winCompat("git remote set-url origin 'https://github.com/composer/composer'")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) ->will($this->returnValue(0)); - $processExecutor->expects($this->at(8)) + $processExecutor->expects($this->at(9)) ->method('execute') ->with($this->equalTo($this->winCompat("git remote set-url --push origin 'git@github.com:composer/composer.git'")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) ->will($this->returnValue(0)); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); + $downloader->download($packageMock, $this->workingDir, $packageMock); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } /** * @group failing - * @expectedException \RuntimeException */ public function testUpdateThrowsRuntimeExceptionIfGitCommandFails() { $expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); + $expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock->expects($this->any()) @@ -513,36 +532,38 @@ composer https://github.com/old/url (push) $packageMock->expects($this->any()) ->method('getVersion') ->will($this->returnValue('1.0.0.0')); - $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $processExecutor->expects($this->at(0)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(1)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(2)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(3)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(4)) - ->method('execute') - ->with($this->equalTo($expectedGitUpdateCommand)) - ->will($this->returnValue(1)); + + $process = $this->prophesize('Composer\Util\ProcessExecutor'); + $process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0); + $process->execute($expectedGitUpdateCommand, null, $this->winCompat($this->workingDir))->willReturn(1)->shouldBeCalled(); + $process->execute($expectedGitUpdateCommand2, null, $this->winCompat($this->workingDir))->willReturn(1)->shouldBeCalled(); + $process->getErrorOutput()->willReturn(''); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); - $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); - $downloader->update($packageMock, $packageMock, $this->workingDir); + + // not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe + try { + $downloader = $this->getDownloaderMock(null, new Config(), $process->reveal()); + $downloader->download($packageMock, $this->workingDir, $packageMock); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); + $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); + $this->fail('This test should throw'); + } catch (\RuntimeException $e) { + if ('RuntimeException' !== get_class($e)) { + throw $e; + } + $this->assertEquals('RuntimeException', get_class($e)); + } } public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover() { - $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); + $expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); $expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)"); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); @@ -554,52 +575,24 @@ composer https://github.com/old/url (push) ->will($this->returnValue('1.0.0.0')); $packageMock->expects($this->any()) ->method('getSourceUrls') - ->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer'))); - $processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock(); - $processExecutor->expects($this->at(0)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git show-ref --head -d"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(1)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(2)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(3)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(4)) - ->method('execute') - ->with($this->equalTo($expectedFirstGitUpdateCommand)) - ->will($this->returnValue(1)); - $processExecutor->expects($this->at(6)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git --version"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(7)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(8)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git remote -v"))) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(9)) - ->method('execute') - ->with($this->equalTo($expectedSecondGitUpdateCommand)) - ->will($this->returnValue(0)); - $processExecutor->expects($this->at(11)) - ->method('execute') - ->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir))) - ->will($this->returnValue(0)); + ->will($this->returnValue(array(Platform::isWindows() ? 'C:\\' : '/', 'https://github.com/composer/composer'))); + + $process = $this->prophesize('Composer\Util\ProcessExecutor'); + $process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0); + $process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0); + $process->execute($expectedFirstGitUpdateCommand, Argument::cetera())->willReturn(1)->shouldBeCalled(); + $process->execute($expectedSecondGitUpdateCommand, Argument::cetera())->willReturn(0)->shouldBeCalled(); + $process->execute($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled(); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); - $downloader = $this->getDownloaderMock(null, new Config(), $processExecutor); + $downloader = $this->getDownloaderMock(null, new Config(), $process->reveal()); + $downloader->download($packageMock, $this->workingDir, $packageMock); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } public function testDowngradeShowsAppropriateMessage() @@ -644,7 +637,10 @@ composer https://github.com/old/url (push) $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); + $downloader->download($newPackage, $this->workingDir, $oldPackage); + $downloader->prepare('update', $newPackage, $this->workingDir, $oldPackage); $downloader->update($oldPackage, $newPackage, $this->workingDir); + $downloader->cleanup('update', $newPackage, $this->workingDir, $oldPackage); } public function testNotUsingDowngradingWithReferences() @@ -679,11 +675,14 @@ composer https://github.com/old/url (push) $ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); $ioMock->expects($this->at(0)) ->method('writeError') - ->with($this->stringContains('updating')); + ->with($this->stringContains('Updating')); $this->fs->ensureDirectoryExists($this->workingDir.'/.git'); $downloader = $this->getDownloaderMock($ioMock, null, $processExecutor); + $downloader->download($newPackage, $this->workingDir, $oldPackage); + $downloader->prepare('update', $newPackage, $this->workingDir, $oldPackage); $downloader->update($oldPackage, $newPackage, $this->workingDir); + $downloader->cleanup('update', $newPackage, $this->workingDir, $oldPackage); } public function testRemove() @@ -703,7 +702,9 @@ composer https://github.com/old/url (push) ->will($this->returnValue(true)); $downloader = $this->getDownloaderMock(null, null, $processExecutor, $filesystem); + $downloader->prepare('uninstall', $packageMock, 'composerPath'); $downloader->remove($packageMock, 'composerPath'); + $downloader->cleanup('uninstall', $packageMock, 'composerPath'); } public function testGetInstallationSource() diff --git a/tests/Composer/Test/Downloader/HgDownloaderTest.php b/tests/Composer/Test/Downloader/HgDownloaderTest.php index a4219d143..c69d497ce 100644 --- a/tests/Composer/Test/Downloader/HgDownloaderTest.php +++ b/tests/Composer/Test/Downloader/HgDownloaderTest.php @@ -98,7 +98,9 @@ class HgDownloaderTest extends TestCase ->will($this->returnValue(null)); $downloader = $this->getDownloaderMock(); + $downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock); $downloader->update($initialPackageMock, $sourcePackageMock, '/path'); + $downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock); } public function testUpdate() @@ -129,7 +131,9 @@ class HgDownloaderTest extends TestCase ->will($this->returnValue(0)); $downloader = $this->getDownloaderMock(null, null, $processExecutor); + $downloader->prepare('update', $packageMock, $this->workingDir, $packageMock); $downloader->update($packageMock, $packageMock, $this->workingDir); + $downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock); } public function testRemove() @@ -148,7 +152,9 @@ class HgDownloaderTest extends TestCase ->will($this->returnValue(true)); $downloader = $this->getDownloaderMock(null, null, $processExecutor, $filesystem); + $downloader->prepare('uninstall', $packageMock, 'composerPath'); $downloader->remove($packageMock, 'composerPath'); + $downloader->cleanup('uninstall', $packageMock, 'composerPath'); } public function testGetInstallationSource() diff --git a/tests/Composer/Test/Downloader/ZipDownloaderTest.php b/tests/Composer/Test/Downloader/ZipDownloaderTest.php index e69149271..63c407218 100644 --- a/tests/Composer/Test/Downloader/ZipDownloaderTest.php +++ b/tests/Composer/Test/Downloader/ZipDownloaderTest.php @@ -338,7 +338,7 @@ class ZipDownloaderTest extends TestCase class MockedZipDownloader extends ZipDownloader { - public function download(PackageInterface $package, $path, $output = true) + public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true) { return; } From 607b487295dba0f6b6063d7fdba33f3b5aa6c863 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Thu, 29 Aug 2019 11:45:19 +0200 Subject: [PATCH 48/48] Fix missing use/undefined var --- src/Composer/Downloader/GitDownloader.php | 2 +- src/Composer/Downloader/VcsDownloader.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index 300749ca8..3d3d947f4 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -134,7 +134,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface $msg = "Checking out ".$this->getShortHash($ref); $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)'; if (getenv('COMPOSER_DISABLE_NETWORK')) { - throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting'); + throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting'); } } diff --git a/src/Composer/Downloader/VcsDownloader.php b/src/Composer/Downloader/VcsDownloader.php index 15bf55072..ce0a4bd9f 100644 --- a/src/Composer/Downloader/VcsDownloader.php +++ b/src/Composer/Downloader/VcsDownloader.php @@ -20,6 +20,7 @@ use Composer\Package\Version\VersionParser; use Composer\Util\ProcessExecutor; use Composer\IO\IOInterface; use Composer\Util\Filesystem; +use React\Promise\PromiseInterface; /** * @author Jordi Boggiano