diff --git a/doc/03-cli.md b/doc/03-cli.md index d5064c5d9..49879d54d 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -920,6 +920,10 @@ If you use a proxy but it does not support the request_fulluri flag for HTTPS requests, then you should set this env var to `false` or `0` to prevent Composer from setting the request_fulluri option. +### COMPOSER_SELF_UPDATE_TARGET + +If set, makes the self-update command write the new Composer phar file into that path instead of overwriting itself. Useful for updating Composer on read-only filesystem. + ### no_proxy or NO_PROXY If you are behind a proxy and would like to disable it for certain domains, you diff --git a/src/Composer/Command/SelfUpdateCommand.php b/src/Composer/Command/SelfUpdateCommand.php index 78b27460e..226aafeb5 100644 --- a/src/Composer/Command/SelfUpdateCommand.php +++ b/src/Composer/Command/SelfUpdateCommand.php @@ -351,6 +351,10 @@ TAGSPUBKEY @copy($localFilename, $backupTarget); } + if ($targetFilename = getenv('COMPOSER_SELF_UPDATE_TARGET')) { + $localFilename = realpath($targetFilename) ?: $targetFilename; + } + rename($newFilename, $localFilename); return null; diff --git a/src/Composer/DependencyResolver/Problem.php b/src/Composer/DependencyResolver/Problem.php index bb497b549..df289a4c1 100644 --- a/src/Composer/DependencyResolver/Problem.php +++ b/src/Composer/DependencyResolver/Problem.php @@ -108,7 +108,7 @@ class Problem $msg = "\n - This package requires ".$packageName.$this->constraintToText($constraint).' but '; - if (defined('HHVM_VERSION') || count($available)) { + if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) { return $msg . 'your HHVM version does not satisfy that requirement.'; } diff --git a/src/Composer/Package/AliasPackage.php b/src/Composer/Package/AliasPackage.php index 09ed4fb9b..89f197856 100644 --- a/src/Composer/Package/AliasPackage.php +++ b/src/Composer/Package/AliasPackage.php @@ -401,4 +401,14 @@ class AliasPackage extends BasePackage implements CompletePackageInterface { return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')'; } + + public function setDistUrl($url) + { + return $this->aliasOf->setDistUrl($url); + } + + public function setDistType($type) + { + return $this->aliasOf->setDistType($type); + } } diff --git a/src/Composer/Package/PackageInterface.php b/src/Composer/Package/PackageInterface.php index 73d2ade41..cb16efa7e 100644 --- a/src/Composer/Package/PackageInterface.php +++ b/src/Composer/Package/PackageInterface.php @@ -358,4 +358,32 @@ interface PackageInterface * @return array */ public function getTransportOptions(); + + /** + * @param string $reference + * + * @return void + */ + public function setSourceReference($reference); + + /** + * @param string $url + * + * @return void + */ + public function setDistUrl($url); + + /** + * @param string $type + * + * @return void + */ + public function setDistType($type); + + /** + * @param string $reference + * + * @return void + */ + public function setDistReference($reference); } diff --git a/src/Composer/Repository/ComposerRepository.php b/src/Composer/Repository/ComposerRepository.php index 38b865103..9d5b727cc 100644 --- a/src/Composer/Repository/ComposerRepository.php +++ b/src/Composer/Repository/ComposerRepository.php @@ -562,7 +562,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito protected function canonicalizeUrl($url) { if ('/' === $url[0]) { - return preg_replace('{(https?://[^/]+).*}i', '$1' . $url, $this->url); + if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) { + return $matches[0] . $url; + } + + return $this->url; } return $url; diff --git a/tests/Composer/Test/Repository/ComposerRepositoryTest.php b/tests/Composer/Test/Repository/ComposerRepositoryTest.php index 3e29e8023..8e9216b35 100644 --- a/tests/Composer/Test/Repository/ComposerRepositoryTest.php +++ b/tests/Composer/Test/Repository/ComposerRepositoryTest.php @@ -204,4 +204,71 @@ class ComposerRepositoryTest extends TestCase $repository->search('foo', RepositoryInterface::SEARCH_FULLTEXT, 'library') ); } + + /** + * @dataProvider canonicalizeUrlProvider + * + * @param string $expected + * @param string $url + * @param string $repositoryUrl + */ + public function testCanonicalizeUrl($expected, $url, $repositoryUrl) + { + $repository = new ComposerRepository( + array('url' => $repositoryUrl), + new NullIO(), + FactoryMock::createConfig() + ); + + $object = new \ReflectionObject($repository); + + $method = $object->getMethod('canonicalizeUrl'); + $method->setAccessible(true); + + // ComposerRepository::__construct ensures that the repository URL has a + // protocol, so reset it here in order to test all cases. + $property = $object->getProperty('url'); + $property->setAccessible(true); + $property->setValue($repository, $repositoryUrl); + + $this->assertSame($expected, $method->invoke($repository, $url)); + } + + public function canonicalizeUrlProvider() + { + return array( + array( + 'https://example.org/path/to/file', + '/path/to/file', + 'https://example.org', + ), + array( + 'https://example.org/canonic_url', + 'https://example.org/canonic_url', + 'https://should-not-see-me.test', + ), + array( + 'file:///path/to/repository/file', + '/path/to/repository/file', + 'file:///path/to/repository', + ), + array( + // Assert that the repository URL is returned unchanged if it is + // not a URL. + // (Backward compatibility test) + 'invalid_repo_url', + '/path/to/file', + 'invalid_repo_url', + ), + array( + // Assert that URLs can contain sequences resembling pattern + // references as understood by preg_replace() without messing up + // the result. + // (Regression test) + 'https://example.org/path/to/unusual_$0_filename', + '/path/to/unusual_$0_filename', + 'https://example.org', + ), + ); + } }