From 4d85e217c30ebcc67618e0978cd99544f7aeb634 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Sat, 16 Feb 2019 18:46:59 +0100 Subject: [PATCH 01/27] 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 9de07bed1b3f4b96e0da90f2caab525073cf5048 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 25 Feb 2019 08:01:38 +0100 Subject: [PATCH 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 09/27] 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 10/27] 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 fedc69f828d138d13eeb3afc32969ec93c497e90 Mon Sep 17 00:00:00 2001 From: Andrey Bolonin Date: Sun, 14 Jul 2019 15:25:01 +0300 Subject: [PATCH 11/27] 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 12/27] 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 13/27] 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 14/27] 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 15/27] 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 16/27] 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 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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 27/27] 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; + } }