1
0
Fork 0

Merge remote-tracking branch 'github-composer/2.0' into solve-without-installed

* github-composer/2.0: (63 commits)
  Fix PSR warnings for optimized autoloader, refs #8397, refs #8403
  Prepare 1.9.1 changelog
  Output a hint that maybe you are not in the right directory, fixes #8404
  Fix PSR warnings for optimized autoloader, refs #8397, refs #8403
  Fix tests for PSR-fix in optimized autoloader, refs #8397
  Fix tests for PSR-fix in optimized autoloader, refs #8397
  Change PSR-fix for optimized autoloader to only warn for now, refs #8397
  Fix output of dump-autoload command to avoid interfering with warnings, refs #8397
  Remove credentials from git remotes in cache and vendor dirs
  Avoid overwriting credentials with existing ones from git repos, refs #8293
  Fix github auth to try https with pwd also, fixes #8356
  Fix gitlab support for basic-auth fallback from ssh URLs
  Avoid clearing the error output during removeDirectory execution, losing git error output, fixes #8351
  Move test file parsing into try/catch block to avoid phpunit swallowing errors
  make optimized autoloader respect PSR standards
  Validate composer show with --tree and --path options set (#8390)
  Don't show root warning for docker containers
  Added phpdoc for ComposerAutoloaderInit$SHA1::getLoader() (#8393)
  Validate schema name, type and version
  Fix require command to allow working on network mounts, fixes #8231
  ...
pull/7936/head
Nils Adermann 2019-11-08 12:27:13 +01:00
commit 97ec2d7b61
59 changed files with 650 additions and 176 deletions

View File

@ -1,3 +1,28 @@
### [1.9.1] 2019-11-01
* Fixed various credential handling issues with gitlab and github
* Fixed credentials being present in git remotes in Composer cache and vendor directory when not using SSH keys
* Fixed `composer why` not listing replacers as a reason something is present
* Fixed various PHP 7.4 compatibility issues
* Fixed root warnings always present in Docker containers, setting COMPOSER_ALLOW_SUPERUSER is not necessary anymore
* Fixed GitHub access tokens leaking into debug-verbosity output
* Fixed several edge case issues detecting GitHub, Bitbucket and GitLab repository types
* Fixed Composer asking if you want to use a composer.json in a parent directory when ran in non-interactive mode
* Fixed classmap autoloading issue finding classes located within a few non-PHP context blocks (?>...<?php)
### [1.9.0] 2019-08-02
* Breaking: artifact repositories with URLs containing port numbers and requiring authentication now require you to configure http-basic auth for the `host:port` pair explicitly
* Added a `--no-cache` flag available on all commands to run with the cache disabled
* Added PHP_BINARY as env var pointing to the PHP process when executing Composer scripts as shell scripts
* Added a `use-github-api` config option which can set the `no-api` flag on all GitHub VCS repositories declared
* Added a static helper you can preprend to a script to avoid process timeouts, `"Composer\\Config::disableProcessTimeout"`
* Added Event::getOriginatingEvent to retrieve an event's original event when a script handler forwards to another one
* Added support for autoloading directly from a phar file
* Fixed loading order of plugins to always initialize them in order of dependencies
* Fixed various network-mount related issues
* Fixed --ignore-platform-reqs not ignoring conflict rules against platform packages
### [1.8.6] 2019-06-11 ### [1.8.6] 2019-06-11
* Fixed handling of backslash-escapes handling in composer.json when using the require command * Fixed handling of backslash-escapes handling in composer.json when using the require command
@ -751,6 +776,8 @@
* Initial release * Initial release
[1.9.1]: https://github.com/composer/composer/compare/1.9.0...1.9.1
[1.9.0]: https://github.com/composer/composer/compare/1.8.6...1.9.0
[1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6 [1.8.6]: https://github.com/composer/composer/compare/1.8.5...1.8.6
[1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5 [1.8.5]: https://github.com/composer/composer/compare/1.8.4...1.8.5
[1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4 [1.8.4]: https://github.com/composer/composer/compare/1.8.3...1.8.4

72
composer.lock generated
View File

@ -8,25 +8,25 @@
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.1.4", "version": "1.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
"reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-openssl": "*", "ext-openssl": "*",
"ext-pcre": "*", "ext-pcre": "*",
"php": "^5.3.2 || ^7.0" "php": "^5.3.2 || ^7.0 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/process": "^2.5 || ^3.0 || ^4.0" "symfony/process": "^2.5 || ^3.0 || ^4.0"
}, },
@ -60,7 +60,7 @@
"ssl", "ssl",
"tls" "tls"
], ],
"time": "2019-01-28T09:30:10+00:00" "time": "2019-08-30T08:44:50+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
@ -126,16 +126,16 @@
}, },
{ {
"name": "composer/spdx-licenses", "name": "composer/spdx-licenses",
"version": "1.5.1", "version": "1.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/spdx-licenses.git", "url": "https://github.com/composer/spdx-licenses.git",
"reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d" "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5",
"reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -182,7 +182,7 @@
"spdx", "spdx",
"validator" "validator"
], ],
"time": "2019-03-26T10:23:26+00:00" "time": "2019-07-29T10:31:59+00:00"
}, },
{ {
"name": "composer/xdebug-handler", "name": "composer/xdebug-handler",
@ -697,16 +697,16 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.11.0", "version": "v1.12.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "82ebae02209c21113908c229e9883c419720738a" "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
"reference": "82ebae02209c21113908c229e9883c419720738a", "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -718,7 +718,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.11-dev" "dev-master": "1.12-dev"
} }
}, },
"autoload": { "autoload": {
@ -734,13 +734,13 @@
"MIT" "MIT"
], ],
"authors": [ "authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
},
{ {
"name": "Gert de Pagter", "name": "Gert de Pagter",
"email": "BackEndTea@gmail.com" "email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony polyfill for ctype functions", "description": "Symfony polyfill for ctype functions",
@ -751,20 +751,20 @@
"polyfill", "polyfill",
"portable" "portable"
], ],
"time": "2019-02-06T07:57:58+00:00" "time": "2019-08-06T08:03:45+00:00"
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.11.0", "version": "v1.12.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609" "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
"reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -776,7 +776,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.11-dev" "dev-master": "1.12-dev"
} }
}, },
"autoload": { "autoload": {
@ -810,7 +810,7 @@
"portable", "portable",
"shim" "shim"
], ],
"time": "2019-02-06T07:57:58+00:00" "time": "2019-08-06T08:03:45+00:00"
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
@ -968,22 +968,22 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "1.8.0", "version": "1.9.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203",
"reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0", "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0" "sebastian/recursion-context": "^1.0|^2.0|^3.0"
}, },
@ -998,8 +998,8 @@
} }
}, },
"autoload": { "autoload": {
"psr-0": { "psr-4": {
"Prophecy\\": "src/" "Prophecy\\": "src/Prophecy"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -1027,7 +1027,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2018-08-05T17:53:17+00:00" "time": "2019-10-03T11:07:50+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",

View File

@ -106,7 +106,8 @@ Linux distributions.
> `mkdir -p /usr/local/bin`. > `mkdir -p /usr/local/bin`.
> **Note:** For information on changing your PATH, please read the > **Note:** For information on changing your PATH, please read the
> [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use Google. > [Wikipedia article](https://en.wikipedia.org/wiki/PATH_(variable)) and/or use
> your search engine of choice.
Now run `composer` in order to run Composer instead of `php composer.phar`. Now run `composer` in order to run Composer instead of `php composer.phar`.
@ -139,7 +140,7 @@ C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
Add the directory to your PATH environment variable if it isn't already. Add the directory to your PATH environment variable if it isn't already.
For information on changing your PATH variable, please see For information on changing your PATH variable, please see
[this article](https://www.computerhope.com/issues/ch000549.htm) and/or [this article](https://www.computerhope.com/issues/ch000549.htm) and/or
use Google. use your search engine of choice.
Close your current terminal. Test usage with a new terminal: Close your current terminal. Test usage with a new terminal:

View File

@ -296,4 +296,9 @@ Example:
Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files Defaults to `true`. If set to `false`, Composer will not create `.htaccess` files
in the composer home, cache, and data directories. in the composer home, cache, and data directories.
## lock
Defaults to `true`. If set to `false`, Composer will not create a `composer.lock`
file.
&larr; [Repositories](05-repositories.md) | [Community](07-community.md) &rarr; &larr; [Repositories](05-repositories.md) | [Community](07-community.md) &rarr;

View File

@ -339,6 +339,9 @@ One limitation of this is that you can not call multiple commands in
a row like `@php install && @php foo`. You must split them up in a a row like `@php install && @php foo`. You must split them up in a
JSON array of commands. JSON array of commands.
You can also call a shell/bash script, which will have the path to
the PHP executable available in it as a `PHP_BINARY` env var.
## Custom descriptions. ## Custom descriptions.
You can set custom script descriptions with the following in your `composer.json`: You can set custom script descriptions with the following in your `composer.json`:

View File

@ -211,6 +211,19 @@ To enable the swap you can use for example:
``` ```
You can make a permanent swap file following this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04). You can make a permanent swap file following this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04).
## proc_open(): failed to open stream errors (Windows)
If composer shows proc_open(NUL) errors on Windows:
`proc_open(NUL): failed to open stream: No such file or directory`
This could be happening because you are working in a _OneDrive_ directory and
using a version of PHP that does not support the file system semantics of this
service. The issue was fixed in PHP 7.2.23 and 7.3.10.
Alternatively it could be because the Windows Null Service is not enabled. For
more information, see this [issue](https://github.com/composer/composer/issues/7186#issuecomment-373134916).
## Degraded Mode ## Degraded Mode
Due to some intermittent issues on Travis and other systems, we introduced a Due to some intermittent issues on Travis and other systems, we introduced a

View File

@ -11,7 +11,8 @@
}, },
"type": { "type": {
"description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.", "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
"type": "string" "type": "string",
"pattern": "^[a-z0-9-]+$"
}, },
"target-dir": { "target-dir": {
"description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.", "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
@ -39,7 +40,8 @@
}, },
"version": { "version": {
"type": "string", "type": "string",
"description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes." "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
"pattern": "^v?\\d+(((\\.\\d+)?\\.\\d+)?\\.\\d+)?"
}, },
"time": { "time": {
"type": "string", "type": "string",
@ -290,6 +292,10 @@
"sort-packages": { "sort-packages": {
"type": "boolean", "type": "boolean",
"description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency." "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
},
"lock": {
"type": "boolean",
"description": "Defaults to true. If set to false, Composer will not create a composer.lock file."
} }
} }
}, },

View File

@ -256,15 +256,14 @@ EOF;
continue; continue;
} }
$namespaceFilter = $namespace === '' ? null : $namespace; $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
} }
} }
} }
} }
foreach ($autoloads['classmap'] as $dir) { foreach ($autoloads['classmap'] as $dir) {
$classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap); $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
} }
ksort($classMap); ksort($classMap);
@ -317,9 +316,9 @@ EOF;
return count($classMap); return count($classMap);
} }
private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array()) private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
{ {
foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) as $class => $path) { foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
$pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n"; $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
if (!isset($classMap[$class])) { if (!isset($classMap[$class])) {
$classMap[$class] = $pathCode; $classMap[$class] = $pathCode;
@ -334,9 +333,9 @@ EOF;
return $classMap; return $classMap;
} }
private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true) private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, $showAmbiguousWarning = true)
{ {
return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter); return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType);
} }
public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages) public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@ -447,7 +446,7 @@ EOF;
foreach ($autoloads['classmap'] as $dir) { foreach ($autoloads['classmap'] as $dir) {
try { try {
$loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false)); $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, null, false));
} catch (\RuntimeException $e) { } catch (\RuntimeException $e) {
$this->io->writeError('<warning>'.$e->getMessage().'</warning>'); $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
} }
@ -592,6 +591,9 @@ class ComposerAutoloaderInit$suffix
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::\$loader) { if (null !== self::\$loader) {

View File

@ -54,13 +54,15 @@ class ClassMapGenerator
* @param string $blacklist Regex that matches against the file path that exclude from the classmap. * @param string $blacklist Regex that matches against the file path that exclude from the classmap.
* @param IOInterface $io IO object * @param IOInterface $io IO object
* @param string $namespace Optional namespace prefix to filter by * @param string $namespace Optional namespace prefix to filter by
* @param string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
* *
* @throws \RuntimeException When the path is neither an existing file nor directory * @throws \RuntimeException When the path is neither an existing file nor directory
* @return array A class map array * @return array A class map array
*/ */
public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null) public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null, $autoloadType = null)
{ {
if (is_string($path)) { if (is_string($path)) {
$basePath = $path;
if (is_file($path)) { if (is_file($path)) {
$path = array(new \SplFileInfo($path)); $path = array(new \SplFileInfo($path));
} elseif (is_dir($path)) { } elseif (is_dir($path)) {
@ -71,6 +73,8 @@ class ClassMapGenerator
'" which does not appear to be a file nor a folder' '" which does not appear to be a file nor a folder'
); );
} }
} elseif (null !== $autoloadType) {
throw new \RuntimeException('Path must be a string when specifying an autoload type');
} }
$map = array(); $map = array();
@ -100,10 +104,14 @@ class ClassMapGenerator
} }
$classes = self::findClasses($filePath); $classes = self::findClasses($filePath);
if (null !== $autoloadType) {
$classes = self::filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath, $io);
}
foreach ($classes as $class) { foreach ($classes as $class) {
// skip classes not within the given namespace prefix // skip classes not within the given namespace prefix
if (null !== $namespace && 0 !== strpos($class, $namespace)) { // TODO enable in Composer v1.11 or 2.0 whichever comes first
if (/* null === $autoloadType && */ null !== $namespace && '' !== $namespace && 0 !== strpos($class, $namespace)) {
continue; continue;
} }
@ -121,6 +129,72 @@ class ClassMapGenerator
return $map; return $map;
} }
/**
* Remove classes which could not have been loaded by namespace autoloaders
*
* @param array $classes found classes in given file
* @param string $filePath current file
* @param string $baseNamespace prefix of given autoload mapping
* @param string $namespaceType psr-0|psr-4
* @param string $basePath root directory of given autoload mapping
* @param IOInterface $io IO object
* @return array valid classes
*/
private static function filterByNamespace($classes, $filePath, $baseNamespace, $namespaceType, $basePath, $io)
{
$validClasses = array();
$rejectedClasses = array();
$realSubPath = substr($filePath, strlen($basePath) + 1);
$realSubPath = substr($realSubPath, 0, strrpos($realSubPath, '.'));
foreach ($classes as $class) {
// silently skip if ns doesn't have common root
if ('' !== $baseNamespace && 0 !== strpos($class, $baseNamespace)) {
continue;
}
// transform class name to file path and validate
if ('psr-0' === $namespaceType) {
$namespaceLength = strrpos($class, '\\');
if (false !== $namespaceLength) {
$namespace = substr($class, 0, $namespaceLength + 1);
$className = substr($class, $namespaceLength + 1);
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
. str_replace('_', DIRECTORY_SEPARATOR, $className);
}
else {
$subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
}
} elseif ('psr-4' === $namespaceType) {
$subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
$subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
} else {
throw new \RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
}
if ($subPath === $realSubPath) {
$validClasses[] = $class;
} else {
$rejectedClasses[] = $class;
}
}
// warn only if no valid classes, else silently skip invalid
if (empty($validClasses)) {
foreach ($rejectedClasses as $class) {
trigger_error(
"Class $class located in ".preg_replace('{^'.preg_quote(getcwd()).'}', '.', $filePath, 1)." does not comply with $namespaceType autoloading standard. It will not autoload anymore in Composer v1.11+.",
E_USER_DEPRECATED
);
}
// TODO enable in Composer v1.11 or 2.0 whichever comes first
//return array();
}
// TODO enable in Composer v1.11 or 2.0 whichever comes first & unskip test in AutoloadGeneratorTest::testPSRToClassMapIgnoresNonPSRClasses
//return $validClasses;
return $classes;
}
/** /**
* Extract the classes in the given file * Extract the classes in the given file
* *
@ -173,7 +247,7 @@ class ClassMapGenerator
} }
} }
// strip non-php blocks in the file // strip non-php blocks in the file
$contents = preg_replace('{\?>.+<\?}s', '?><?', $contents); $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
// strip trailing non-php code if needed // strip trailing non-php code if needed
$pos = strrpos($contents, '?>'); $pos = strrpos($contents, '?>');
if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) { if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {

View File

@ -42,5 +42,7 @@ EOT
See https://getcomposer.org/ for more information.</comment> See https://getcomposer.org/ for more information.</comment>
EOT EOT
); );
return 0;
} }
} }

View File

@ -62,7 +62,7 @@ class BaseDependencyCommand extends BaseCommand
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
* @param bool $inverted Whether to invert matching process (why-not vs why behaviour) * @param bool $inverted Whether to invert matching process (why-not vs why behaviour)
* @return int|null Exit code of the operation. * @return int Exit code of the operation.
*/ */
protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false) protected function doExecute(InputInterface $input, OutputInterface $output, $inverted = false)
{ {

View File

@ -70,5 +70,7 @@ EOT
} }
$io->writeError('<info>All caches cleared.</info>'); $io->writeError('<info>All caches cleared.</info>');
return 0;
} }
} }

View File

@ -412,6 +412,7 @@ EOT
), ),
'github-expose-hostname' => array($booleanValidator, $booleanNormalizer), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
'htaccess-protect' => array($booleanValidator, $booleanNormalizer), 'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
'lock' => array($booleanValidator, $booleanNormalizer),
); );
$multiConfigValues = array( $multiConfigValues = array(
'github-protocols' => array( 'github-protocols' => array(
@ -463,13 +464,19 @@ EOT
$this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>'); $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
} }
return $this->configSource->removeConfigSetting($settingKey); $this->configSource->removeConfigSetting($settingKey);
return 0;
} }
if (isset($uniqueConfigValues[$settingKey])) { if (isset($uniqueConfigValues[$settingKey])) {
return $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting'); $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');
return 0;
} }
if (isset($multiConfigValues[$settingKey])) { if (isset($multiConfigValues[$settingKey])) {
return $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting'); $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');
return 0;
} }
// handle properties // handle properties
@ -530,38 +537,51 @@ EOT
throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json'); throw new \InvalidArgumentException('The '.$settingKey.' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
} }
if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) { if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
return $this->configSource->removeProperty($settingKey); $this->configSource->removeProperty($settingKey);
return 0;
} }
if (isset($uniqueProps[$settingKey])) { if (isset($uniqueProps[$settingKey])) {
return $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty'); $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');
return 0;
} }
if (isset($multiProps[$settingKey])) { if (isset($multiProps[$settingKey])) {
return $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty'); $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');
return 0;
} }
// handle repositories // handle repositories
if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) { if (preg_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {
return $this->configSource->removeRepository($matches[1]); $this->configSource->removeRepository($matches[1]);
return 0;
} }
if (2 === count($values)) { if (2 === count($values)) {
return $this->configSource->addRepository($matches[1], array( $this->configSource->addRepository($matches[1], array(
'type' => $values[0], 'type' => $values[0],
'url' => $values[1], 'url' => $values[1],
)); ));
return 0;
} }
if (1 === count($values)) { if (1 === count($values)) {
$value = strtolower($values[0]); $value = strtolower($values[0]);
if (true === $booleanValidator($value)) { if (true === $booleanValidator($value)) {
if (false === $booleanNormalizer($value)) { if (false === $booleanNormalizer($value)) {
return $this->configSource->addRepository($matches[1], false); $this->configSource->addRepository($matches[1], false);
return 0;
} }
} else { } else {
$value = JsonFile::parseJson($values[0]); $value = JsonFile::parseJson($values[0]);
$this->configSource->addRepository($matches[1], $value);
return $this->configSource->addRepository($matches[1], $value); return 0;
} }
} }
@ -571,22 +591,32 @@ EOT
// handle extra // handle extra
if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) { if (preg_match('/^extra\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {
return $this->configSource->removeProperty($settingKey); $this->configSource->removeProperty($settingKey);
return 0;
} }
return $this->configSource->addProperty($settingKey, $values[0]); $this->configSource->addProperty($settingKey, $values[0]);
return 0;
} }
// handle platform // handle platform
if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) { if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {
return $this->configSource->removeConfigSetting($settingKey); $this->configSource->removeConfigSetting($settingKey);
return 0;
} }
return $this->configSource->addConfigSetting($settingKey, $values[0]); $this->configSource->addConfigSetting($settingKey, $values[0]);
return 0;
} }
if ($settingKey === 'platform' && $input->getOption('unset')) { if ($settingKey === 'platform' && $input->getOption('unset')) {
return $this->configSource->removeConfigSetting($settingKey); $this->configSource->removeConfigSetting($settingKey);
return 0;
} }
// handle auth // handle auth
@ -595,7 +625,7 @@ EOT
$this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]); $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
return; return 0;
} }
if ($matches[1] === 'bitbucket-oauth') { if ($matches[1] === 'bitbucket-oauth') {
@ -618,16 +648,20 @@ EOT
$this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1])); $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
} }
return; return 0;
} }
// handle script // handle script
if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) { if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
if ($input->getOption('unset')) { if ($input->getOption('unset')) {
return $this->configSource->removeProperty($settingKey); $this->configSource->removeProperty($settingKey);
return 0;
} }
return $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]); $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);
return 0;
} }
throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command'); throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');

View File

@ -48,7 +48,7 @@ EOT
* *
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
* @return int|null * @return int
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {

View File

@ -63,11 +63,11 @@ EOT
$apcu = $input->getOption('apcu') || $config->get('apcu-autoloader'); $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');
if ($authoritative) { if ($authoritative) {
$this->getIO()->writeError('<info>Generating optimized autoload files (authoritative)</info>', false); $this->getIO()->write('<info>Generating optimized autoload files (authoritative)</info>');
} elseif ($optimize) { } elseif ($optimize) {
$this->getIO()->writeError('<info>Generating optimized autoload files</info>', false); $this->getIO()->write('<info>Generating optimized autoload files</info>');
} else { } else {
$this->getIO()->writeError('<info>Generating autoload files</info>', false); $this->getIO()->write('<info>Generating autoload files</info>');
} }
$generator = $composer->getAutoloadGenerator(); $generator = $composer->getAutoloadGenerator();
@ -78,11 +78,13 @@ EOT
$numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize); $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);
if ($authoritative) { if ($authoritative) {
$this->getIO()->overwriteError('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>'); $this->getIO()->write('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
} elseif ($optimize) { } elseif ($optimize) {
$this->getIO()->overwriteError('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>'); $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
} else { } else {
$this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>'); $this->getIO()->write('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
} }
return 0;
} }
} }

View File

@ -152,6 +152,8 @@ EOT
if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) { if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
$this->installDependencies($output); $this->installDependencies($output);
} }
return 0;
} }
/** /**
@ -400,7 +402,7 @@ EOT
return $this->repos; return $this->repos;
} }
protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true) final protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true, $fixed = false)
{ {
if ($requires) { if ($requires) {
$requires = $this->normalizeRequirements($requires); $requires = $this->normalizeRequirements($requires);
@ -410,7 +412,7 @@ EOT
foreach ($requires as $requirement) { foreach ($requires as $requirement) {
if (!isset($requirement['version'])) { if (!isset($requirement['version'])) {
// determine the best version automatically // determine the best version automatically
list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability); list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, null, null, $fixed);
$requirement['version'] = $version; $requirement['version'] = $version;
// replace package name from packagist.org // replace package name from packagist.org
@ -423,7 +425,7 @@ EOT
)); ));
} else { } else {
// check that the specified version/constraint exists before we proceed // check that the specified version/constraint exists before we proceed
list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev'); list($name, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev', $fixed);
// replace package name from packagist.org // replace package name from packagist.org
$requirement['name'] = $name; $requirement['name'] = $name;
@ -700,10 +702,11 @@ EOT
* @param string $preferredStability * @param string $preferredStability
* @param string|null $requiredVersion * @param string|null $requiredVersion
* @param string $minimumStability * @param string $minimumStability
* @param bool $fixed
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @return array name version * @return array name version
*/ */
private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null) private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null, $fixed = null)
{ {
// find the latest version allowed in this repo set // find the latest version allowed in this repo set
$versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability)); $versionSelector = new VersionSelector($this->getRepositorySet($input, $minimumStability));
@ -777,7 +780,7 @@ EOT
return array( return array(
$package->getPrettyName(), $package->getPrettyName(),
$versionSelector->findRecommendedRequireVersion($package), $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
); );
} }

View File

@ -110,6 +110,8 @@ EOT
default: default:
throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format)); throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format));
} }
return 0;
} }
/** /**

View File

@ -59,7 +59,7 @@ EOT
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$args = array( $args = array(
'show', 'command' => 'show',
'--latest' => true, '--latest' => true,
); );
if (!$input->getOption('all')) { if (!$input->getOption('all')) {

View File

@ -48,7 +48,7 @@ EOT
* *
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
* @return int|null * @return int
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {

View File

@ -49,6 +49,7 @@ class RequireCommand extends InitCommand
new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'), new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'), new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'), new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'), new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'), new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'), new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
@ -99,7 +100,9 @@ EOT
return 1; return 1;
} }
if (!is_readable($this->file)) { // check for readability by reading the file as is_readable 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_readable($this->file) && false === Silencer::call('file_get_contents', $this->file)) {
$io->writeError('<error>'.$this->file.' is not readable.</error>'); $io->writeError('<error>'.$this->file.' is not readable.</error>');
return 1; return 1;
@ -120,6 +123,25 @@ EOT
return 1; return 1;
} }
if ($input->getOption('fixed') === true) {
$config = $this->json->read();
$packageType = empty($config['type']) ? 'library' : $config['type'];
/**
* @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
*/
if ($packageType !== 'project') {
$io->writeError('<error>"--fixed" option is allowed for "project" package types only to prevent possible misuses.</error>');
if (empty($config['type'])) {
$io->writeError('<error>If your package is not library, you should explicitly specify "type" parameter in composer.json.</error>');
}
return 1;
}
}
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
$repos = $composer->getRepositoryManager()->getRepositories(); $repos = $composer->getRepositoryManager()->getRepositories();
@ -137,7 +159,15 @@ EOT
} }
$phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion(); $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update')); try {
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'), $input->getOption('fixed'));
} catch (\Exception $e) {
if ($this->newlyCreated) {
throw new \RuntimeException('No composer.json present in the current directory, this may be the cause of the following exception.', 0, $e);
}
throw $e;
}
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require'; $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev'; $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

View File

@ -79,5 +79,7 @@ EOT
foreach ($results as $result) { foreach ($results as $result) {
$io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : '')); $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
} }
return 0;
} }
} }

View File

@ -254,6 +254,8 @@ TAGSPUBKEY
} else { } else {
$io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>'); $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
} }
return 0;
} }
protected function fetchKeys(IOInterface $io, Config $config) protected function fetchKeys(IOInterface $io, Config $config)

View File

@ -129,6 +129,12 @@ EOT
return 1; return 1;
} }
if ($input->getOption('tree') && $input->getOption('path')) {
$io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');
return 1;
}
$format = $input->getOption('format'); $format = $input->getOption('format');
if (!in_array($format, array('text', 'json'))) { if (!in_array($format, array('text', 'json'))) {
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format)); $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
@ -586,6 +592,7 @@ EOT
} }
$io->write('<info>type</info> : ' . $package->getType()); $io->write('<info>type</info> : ' . $package->getType());
$this->printLicenses($package); $this->printLicenses($package);
$io->write('<info>homepage</info> : ' . $package->getHomepage());
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference())); $io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference())); $io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
if ($installedRepo->hasPackage($package)) { if ($installedRepo->hasPackage($package)) {

View File

@ -61,7 +61,7 @@ EOT
/** /**
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
* @return int|null * @return int
*/ */
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {

View File

@ -93,7 +93,7 @@ EOT
continue; continue;
} }
foreach ($package['suggest'] as $suggestion => $reason) { foreach ($package['suggest'] as $suggestion => $reason) {
if (false === strpos('/', $suggestion) && null !== $platform->findPackage($suggestion, '*')) { if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
continue; continue;
} }
if (!isset($installed[$suggestion])) { if (!isset($installed[$suggestion])) {
@ -121,7 +121,7 @@ EOT
$io->write(sprintf('<info>%s</info>', $suggestion)); $io->write(sprintf('<info>%s</info>', $suggestion));
} }
return null; return 0;
} }
// Grouped by package // Grouped by package
@ -151,5 +151,7 @@ EOT
$io->write(''); $io->write('');
} }
} }
return 0;
} }
} }

View File

@ -63,6 +63,7 @@ class Config
'archive-dir' => '.', 'archive-dir' => '.',
'htaccess-protect' => true, 'htaccess-protect' => true,
'use-github-api' => true, 'use-github-api' => true,
'lock' => true,
// valid keys without defaults (auth config stuff): // valid keys without defaults (auth config stuff):
// bitbucket-oauth // bitbucket-oauth
// github-oauth // github-oauth
@ -329,6 +330,8 @@ class Config
return $this->config[$key] !== 'false' && (bool) $this->config[$key]; return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'use-github-api': case 'use-github-api':
return $this->config[$key] !== 'false' && (bool) $this->config[$key]; return $this->config[$key] !== 'false' && (bool) $this->config[$key];
case 'lock':
return $this->config[$key] !== 'false' && (bool) $this->config[$key];
default: default:
if (!isset($this->config[$key])) { if (!isset($this->config[$key])) {
return null; return null;

View File

@ -113,6 +113,10 @@ class Application extends BaseApplication
{ {
$this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins'); $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
if (getenv('COMPOSER_NO_INTERACTION')) {
$input->setInteractive(false);
}
$io = $this->io = new ConsoleIO($input, $output, new HelperSet(array( $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
new QuestionHelper(), new QuestionHelper(),
))); )));
@ -208,11 +212,7 @@ class Application extends BaseApplication
$io->writeError(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF'])); $io->writeError(sprintf('<warning>Warning: This development build of composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
} }
if (getenv('COMPOSER_NO_INTERACTION')) { if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER') && !file_exists('/.dockerenv')) {
$input->setInteractive(false);
}
if (!Platform::isWindows() && function_exists('exec') && !getenv('COMPOSER_ALLOW_SUPERUSER')) {
if (function_exists('posix_getuid') && posix_getuid() === 0) { if (function_exists('posix_getuid') && posix_getuid() === 0) {
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') { if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
$io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>'); $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

View File

@ -183,7 +183,7 @@ class Decisions implements \Iterator, \Countable
$previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null; $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
if ($previousDecision != 0) { if ($previousDecision != 0) {
$literalString = $this->pool->literalToString($literal); $literalString = $this->pool->literalToPrettyString($literal, array());
$package = $this->pool->literalToPackage($literal); $package = $this->pool->literalToPackage($literal);
throw new SolverBugException( throw new SolverBugException(
"Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."." "Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."

View File

@ -74,10 +74,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$command = $command =
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% ' 'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
. '&& cd '.$flag.'%path% ' . '&& cd '.$flag.'%path% '
. '&& git remote set-url origin %url% && git remote add composer %url%'; . '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
} else { } else {
$msg = "Cloning ".$this->getShortHash($ref); $msg = "Cloning ".$this->getShortHash($ref);
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer'; $command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer && git remote set-url origin %sanitizedUrl% && git remote set-url composer %sanitizedUrl%';
if (getenv('COMPOSER_DISABLE_NETWORK')) { 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 '.$package->getName().' is not in cache and network is disabled, aborting');
} }
@ -87,11 +87,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$commandCallable = function ($url) use ($path, $command, $cachePath) { $commandCallable = function ($url) use ($path, $command, $cachePath) {
return str_replace( return str_replace(
array('%url%', '%path%', '%cachePath%'), array('%url%', '%path%', '%cachePath%', '%sanitizedUrl%'),
array( array(
ProcessExecutor::escape($url), ProcessExecutor::escape($url),
ProcessExecutor::escape($path), ProcessExecutor::escape($path),
ProcessExecutor::escape($cachePath), ProcessExecutor::escape($cachePath),
ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
), ),
$command $command
); );
@ -129,10 +130,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
if (!empty($this->cachedPackages[$target->getId()][$ref])) { if (!empty($this->cachedPackages[$target->getId()][$ref])) {
$msg = "Checking out ".$this->getShortHash($ref).' from cache'; $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%'; $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 %sanitizedUrl%';
} else { } else {
$msg = "Checking out ".$this->getShortHash($ref); $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)'; $command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer); git remote set-url composer %sanitizedUrl%';
if (getenv('COMPOSER_DISABLE_NETWORK')) { if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$target->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');
} }
@ -142,11 +143,12 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$commandCallable = function ($url) use ($ref, $command, $cachePath) { $commandCallable = function ($url) use ($ref, $command, $cachePath) {
return str_replace( return str_replace(
array('%url%', '%ref%', '%cachePath%'), array('%url%', '%ref%', '%cachePath%', '%sanitizedUrl%'),
array( array(
ProcessExecutor::escape($url), ProcessExecutor::escape($url),
ProcessExecutor::escape($ref.'^{commit}'), ProcessExecutor::escape($ref.'^{commit}'),
ProcessExecutor::escape($cachePath), ProcessExecutor::escape($cachePath),
ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url)),
), ),
$command $command
); );

View File

@ -130,7 +130,7 @@ class Installer
protected $preferStable = false; protected $preferStable = false;
protected $preferLowest = false; protected $preferLowest = false;
protected $skipSuggest = false; protected $skipSuggest = false;
protected $writeLock = true; protected $writeLock;
protected $executeOperations = true; protected $executeOperations = true;
/** /**
@ -177,6 +177,8 @@ class Installer
$this->installationManager = $installationManager; $this->installationManager = $installationManager;
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->autoloadGenerator = $autoloadGenerator; $this->autoloadGenerator = $autoloadGenerator;
$this->writeLock = $config->get('lock');
} }
/** /**

View File

@ -98,6 +98,27 @@ abstract class BaseRepository implements RepositoryInterface
// Replacements are considered valid reasons for a package to be installed during forward resolution // Replacements are considered valid reasons for a package to be installed during forward resolution
if (!$invert) { if (!$invert) {
$links += $package->getReplaces(); $links += $package->getReplaces();
// On forward search, check if any replaced package was required and add the replaced
// packages to the list of needles. Contrary to the cross-reference link check below,
// replaced packages are the target of links.
foreach ($package->getReplaces() as $link) {
foreach ($needles as $needle) {
if ($link->getSource() === $needle) {
if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
// already displayed this node's dependencies, cutting short
if (in_array($link->getTarget(), $packagesInTree)) {
$results[] = array($package, $link, false);
continue;
}
$packagesInTree[] = $link->getTarget();
$dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : array();
$results[] = array($package, $link, $dependents);
$needles[] = $link->getTarget();
}
}
}
}
} }
// Require-dev is only relevant for the root package // Require-dev is only relevant for the root package
@ -112,12 +133,12 @@ abstract class BaseRepository implements RepositoryInterface
if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) { if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
// already displayed this node's dependencies, cutting short // already displayed this node's dependencies, cutting short
if (in_array($link->getSource(), $packagesInTree)) { if (in_array($link->getSource(), $packagesInTree)) {
$results[$link->getSource()] = array($package, $link, false); $results[] = array($package, $link, false);
continue; continue;
} }
$packagesInTree[] = $link->getSource(); $packagesInTree[] = $link->getSource();
$dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array(); $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : array();
$results[$link->getSource()] = array($package, $link, $dependents); $results[] = array($package, $link, $dependents);
} }
} }
} }

View File

@ -125,7 +125,13 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
{ {
parent::initialize(); parent::initialize();
foreach ($this->getUrlMatches() as $url) { $urlMatches = $this->getUrlMatches();
if (empty($urlMatches)) {
throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
}
foreach ($urlMatches as $url) {
$path = realpath($url) . DIRECTORY_SEPARATOR; $path = realpath($url) . DIRECTORY_SEPARATOR;
$composerFilePath = $path.'composer.json'; $composerFilePath = $path.'composer.json';
@ -155,7 +161,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
if (!isset($package['version'])) { if (!isset($package['version'])) {
$versionData = $this->versionGuesser->guessVersion($package, $path); $versionData = $this->versionGuesser->guessVersion($package, $path);
$package['version'] = $versionData['pretty_version'] ?: 'dev-master'; if (is_array($versionData) && $versionData['pretty_version']) {
$package['version'] = $versionData['pretty_version'];
} else {
$package['version'] = 'dev-master';
}
} }
$output = ''; $output = '';

View File

@ -47,7 +47,7 @@ abstract class BitbucketDriver extends VcsDriver
*/ */
public function initialize() public function initialize()
{ {
preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#', $this->url, $match); preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)$#i', $this->url, $match);
$this->owner = $match[1]; $this->owner = $match[1];
$this->repository = $match[2]; $this->repository = $match[2];
$this->originUrl = 'bitbucket.org'; $this->originUrl = 'bitbucket.org';

View File

@ -53,7 +53,7 @@ class GitBitbucketDriver extends BitbucketDriver
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{ {
if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
return false; return false;
} }

View File

@ -48,10 +48,10 @@ class GitHubDriver extends VcsDriver
*/ */
public function initialize() public function initialize()
{ {
preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match); preg_match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $this->url, $match);
$this->owner = $match[3]; $this->owner = $match[3];
$this->repository = $match[4]; $this->repository = $match[4];
$this->originUrl = !empty($match[1]) ? $match[1] : $match[2]; $this->originUrl = strtolower(!empty($match[1]) ? $match[1] : $match[2]);
if ($this->originUrl === 'www.github.com') { if ($this->originUrl === 'www.github.com') {
$this->originUrl = 'github.com'; $this->originUrl = 'github.com';
} }
@ -270,12 +270,12 @@ class GitHubDriver extends VcsDriver
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{ {
if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) { if (!preg_match('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#', $url, $matches)) {
return false; return false;
} }
$originUrl = !empty($matches[2]) ? $matches[2] : $matches[3]; $originUrl = !empty($matches[2]) ? $matches[2] : $matches[3];
if (!in_array(preg_replace('{^www\.}i', '', $originUrl), $config->get('github-domains'))) { if (!in_array(strtolower(preg_replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
return false; return false;
} }

View File

@ -497,6 +497,8 @@ class GitLabDriver extends VcsDriver
*/ */
private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber) private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
{ {
$guessedDomain = strtolower($guessedDomain);
if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) { if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
if ($portNumber) { if ($portNumber) {
return $guessedDomain.':'.$portNumber; return $guessedDomain.':'.$portNumber;

View File

@ -53,7 +53,7 @@ class HgBitbucketDriver extends BitbucketDriver
*/ */
public static function supports(IOInterface $io, Config $config, $url, $deep = false) public static function supports(IOInterface $io, Config $config, $url, $deep = false)
{ {
if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) { if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
return false; return false;
} }

View File

@ -71,7 +71,7 @@ class HgDriver extends VcsDriver
return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir)); return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
}; };
$hgUtils->runCommand($command, $this->url, $this->repoDir); $hgUtils->runCommand($command, $this->url, null);
} }
} }

View File

@ -23,6 +23,7 @@ class AuthHelper
{ {
protected $io; protected $io;
protected $config; protected $config;
private $displayedOriginAuthentications = array();
public function __construct(IOInterface $io, Config $config) public function __construct(IOInterface $io, Config $config)
{ {
@ -116,7 +117,7 @@ class AuthHelper
$message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit'); $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
$gitLabUtil = new GitLab($this->io, $this->config, null); $gitLabUtil = new GitLab($this->io, $this->config, null);
if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && $auth['password'] === 'private-token') { if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token'), true)) {
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
} }
@ -172,7 +173,7 @@ class AuthHelper
throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode); throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
} }
$this->io->writeError(' Authentication required (<info>'.parse_url($url, PHP_URL_HOST).'</info>):'); $this->io->writeError(' Authentication required (<info>'.$origin.'</info>):');
$username = $this->io->ask(' Username: '); $username = $this->io->ask(' Username: ');
$password = $this->io->askAndHideAnswer(' Password: '); $password = $this->io->askAndHideAnswer(' Password: ');
$this->io->setAuthentication($origin, $username, $password); $this->io->setAuthentication($origin, $username, $password);
@ -193,14 +194,18 @@ class AuthHelper
public function addAuthenticationHeader(array $headers, $origin, $url) public function addAuthenticationHeader(array $headers, $origin, $url)
{ {
if ($this->io->hasAuthentication($origin)) { if ($this->io->hasAuthentication($origin)) {
$authenticationDisplayMessage = null;
$auth = $this->io->getAuthentication($origin); $auth = $this->io->getAuthentication($origin);
if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) { if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
$headers[] = 'Authorization: token '.$auth['username']; $headers[] = 'Authorization: token '.$auth['username'];
$authenticationDisplayMessage = 'Using GitHub token authentication';
} elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) { } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
if ($auth['password'] === 'oauth2') { if ($auth['password'] === 'oauth2') {
$headers[] = 'Authorization: Bearer '.$auth['username']; $headers[] = 'Authorization: Bearer '.$auth['username'];
} elseif ($auth['password'] === 'private-token') { $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
} elseif ($auth['password'] === 'private-token' || $auth['password'] === 'gitlab-ci-token') {
$headers[] = 'PRIVATE-TOKEN: '.$auth['username']; $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
$authenticationDisplayMessage = 'Using GitLab private token authentication';
} }
} elseif ( } elseif (
'bitbucket.org' === $origin 'bitbucket.org' === $origin
@ -209,10 +214,17 @@ class AuthHelper
) { ) {
if (!$this->isPublicBitBucketDownload($url)) { if (!$this->isPublicBitBucketDownload($url)) {
$headers[] = 'Authorization: Bearer ' . $auth['password']; $headers[] = 'Authorization: Bearer ' . $auth['password'];
$authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
} }
} else { } else {
$authStr = base64_encode($auth['username'] . ':' . $auth['password']); $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
$headers[] = 'Authorization: Basic '.$authStr; $headers[] = 'Authorization: Basic '.$authStr;
$authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
}
if ($authenticationDisplayMessage && !in_array($origin, $this->displayedOriginAuthentications, true)) {
$this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
$this->displayedOriginAuthentications[] = $origin;
} }
} }
@ -243,4 +255,15 @@ class AuthHelper
return count($pathParts) >= 4 && $pathParts[3] == 'downloads'; return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
} }
/**
* @param string $url
* @return string
*/
public function stripCredentialsFromUrl($url)
{
// GitHub repository rename result in redirect locations containing the access_token as GET parameter
// e.g. https://api.github.com/repositories/9999999999?access_token=github_token
return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
}
} }

View File

@ -687,12 +687,14 @@ class Filesystem
if (!Platform::isWindows()) { if (!Platform::isWindows()) {
return false; return false;
} }
// Important to clear all caches first
clearstatcache(true, $junction);
if (!is_dir($junction) || is_link($junction)) { if (!is_dir($junction) || is_link($junction)) {
return false; return false;
} }
// Important to clear all caches first
clearstatcache(true, $junction);
$stat = lstat($junction); $stat = lstat($junction);
// S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask) // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)

View File

@ -54,9 +54,9 @@ class Git
} }
if (!$initialClone) { if (!$initialClone) {
// capture username/password from URL if there is one // capture username/password from URL if there is one and we have no auth configured yet
$this->process->execute('git remote -v', $output, $cwd); $this->process->execute('git remote -v', $output, $cwd);
if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match)) { if (preg_match('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
$this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2])); $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
} }
} }
@ -95,8 +95,10 @@ class Git
$auth = null; $auth = null;
if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) { if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
// private github repository without git access, try https with auth // private github repository without ssh key access, try https with auth
if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)) { if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
|| preg_match('{^(https?)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)
) {
if (!$this->io->hasAuthentication($match[1])) { if (!$this->io->hasAuthentication($match[1])) {
$gitHubUtil = new GitHub($this->io, $this->config, $this->process); $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
$message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos'; $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';
@ -153,7 +155,14 @@ class Git
return; return;
} }
} }
} elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) { } elseif (
preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
|| preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)
) {
if ($match[1] === 'git') {
$match[1] = 'https';
}
if (!$this->io->hasAuthentication($match[2])) { if (!$this->io->hasAuthentication($match[2])) {
$gitLabUtil = new GitLab($this->io, $this->config, $this->process); $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
$message = 'Cloning failed, enter your GitLab credentials to access private repos'; $message = 'Cloning failed, enter your GitLab credentials to access private repos';
@ -165,17 +174,18 @@ class Git
if ($this->io->hasAuthentication($match[2])) { if ($this->io->hasAuthentication($match[2])) {
$auth = $this->io->getAuthentication($match[2]); $auth = $this->io->getAuthentication($match[2]);
if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') { if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
$authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
} else { } else {
$authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3]; $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
} }
$command = call_user_func($commandCallable, $authUrl); $command = call_user_func($commandCallable, $authUrl);
if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) { if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
return; return;
} }
} }
} elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate
if (strpos($match[2], '@')) { if (strpos($match[2], '@')) {
list($authParts, $match[2]) = explode('@', $match[2], 2); list($authParts, $match[2]) = explode('@', $match[2], 2);
} }
@ -193,7 +203,7 @@ class Git
} }
} }
$this->io->writeError(' Authentication required (<info>' . parse_url($url, PHP_URL_HOST) . '</info>):'); $this->io->writeError(' Authentication required (<info>' . $match[2] . '</info>):');
$auth = array( $auth = array(
'username' => $this->io->ask(' Username: ', $defaultUsername), 'username' => $this->io->ask(' Username: ', $defaultUsername),
'password' => $this->io->askAndHideAnswer(' Password: '), 'password' => $this->io->askAndHideAnswer(' Password: '),
@ -215,10 +225,12 @@ class Git
} }
} }
$errorMsg = $this->process->getErrorOutput();
if ($initialClone) { if ($initialClone) {
$this->filesystem->removeDirectory($origCwd); $this->filesystem->removeDirectory($origCwd);
} }
$this->throwException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(), $url);
$this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
} }
} }
@ -232,7 +244,9 @@ class Git
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
try { try {
$commandCallable = function ($url) { $commandCallable = function ($url) {
return sprintf('git remote set-url origin %s && git remote update --prune origin', ProcessExecutor::escape($url)); $sanitizedUrl = preg_replace('{://([^@]+?):(.+?)@}', '://', $url);
return sprintf('git remote set-url origin %s && git remote update --prune origin && git remote set-url origin %s', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
}; };
$this->runCommand($commandCallable, $url, $dir); $this->runCommand($commandCallable, $url, $dir);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -255,16 +269,29 @@ class Git
} }
public function fetchRefOrSyncMirror($url, $dir, $ref) public function fetchRefOrSyncMirror($url, $dir, $ref)
{
if ($this->checkRefIsInMirror($url, $dir, $ref)) {
return true;
}
if ($this->syncMirror($url, $dir)) {
return $this->checkRefIsInMirror($url, $dir, $ref);
}
return false;
}
private function checkRefIsInMirror($url, $dir, $ref)
{ {
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') { if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
$escapedRef = ProcessExecutor::escape($ref.'^{commit}'); $escapedRef = ProcessExecutor::escape($ref.'^{commit}');
$exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $output, $dir); $exitCode = $this->process->execute(sprintf('git rev-parse --quiet --verify %s', $escapedRef), $ignoredOutput, $dir);
if ($exitCode === 0) { if ($exitCode === 0) {
return true; return true;
} }
} }
return $this->syncMirror($url, $dir); return false;
} }
private function isAuthenticationFailure($url, &$match) private function isAuthenticationFailure($url, &$match)

View File

@ -195,7 +195,7 @@ class CurlDownloader
$usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : ''; $usingProxy = !empty($options['http']['proxy']) ? ' using proxy ' . $options['http']['proxy'] : '';
$ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : ''; $ifModified = false !== strpos(strtolower(implode(',', $options['http']['header'])), 'if-modified-since:') ? ' if modified' : '';
if ($attributes['redirects'] === 0) { if ($attributes['redirects'] === 0) {
$this->io->writeError('Downloading ' . $url . $usingProxy . $ifModified, true, IOInterface::DEBUG); $this->io->writeError('Downloading ' . $this->authHelper->stripCredentialsFromUrl($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
} }
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle)); $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
@ -254,12 +254,12 @@ class CurlDownloader
$contents = stream_get_contents($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']);
} }
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
} else { } else {
rewind($job['bodyHandle']); rewind($job['bodyHandle']);
$contents = stream_get_contents($job['bodyHandle']); $contents = stream_get_contents($job['bodyHandle']);
$response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents); $response = new Response(array('url' => $progress['url']), $statusCode, $headers, $contents);
$this->io->writeError('['.$statusCode.'] '.$progress['url'], true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.$this->authHelper->stripCredentialsFromUrl($progress['url']), true, IOInterface::DEBUG);
} }
fclose($job['bodyHandle']); fclose($job['bodyHandle']);
@ -362,7 +362,7 @@ class CurlDownloader
} }
if (!empty($targetUrl)) { if (!empty($targetUrl)) {
$this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $targetUrl), true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
return $targetUrl; return $targetUrl;
} }

View File

@ -246,7 +246,7 @@ class RemoteFilesystem
$actualContextOptions = stream_context_get_options($ctx); $actualContextOptions = stream_context_get_options($ctx);
$usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : ''; $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
$this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG); $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->authHelper->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
unset($origFileUrl, $actualContextOptions); unset($origFileUrl, $actualContextOptions);
// Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
@ -704,7 +704,7 @@ class RemoteFilesystem
$this->redirects++; $this->redirects++;
$this->io->writeError('', true, IOInterface::DEBUG); $this->io->writeError('', true, IOInterface::DEBUG);
$this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $targetUrl), true, IOInterface::DEBUG); $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->authHelper->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);
$additionalOptions['redirects'] = $this->redirects; $additionalOptions['redirects'] = $this->redirects;

View File

@ -21,7 +21,6 @@ class Zip
* Gets content of the root composer.json inside a ZIP archive. * Gets content of the root composer.json inside a ZIP archive.
* *
* @param string $pathToZip * @param string $pathToZip
* @param string $filename
* *
* @return string|null * @return string|null
*/ */

View File

@ -25,12 +25,18 @@ class ApplicationTest extends TestCase
$inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
$outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
putenv('COMPOSER_NO_INTERACTION=1');
$index = 0; $index = 0;
$inputMock->expects($this->at($index++)) $inputMock->expects($this->at($index++))
->method('hasParameterOption') ->method('hasParameterOption')
->with($this->equalTo('--no-plugins')) ->with($this->equalTo('--no-plugins'))
->will($this->returnValue(true)); ->will($this->returnValue(true));
$inputMock->expects($this->at($index++))
->method('setInteractive')
->with($this->equalTo(false));
$inputMock->expects($this->at($index++)) $inputMock->expects($this->at($index++))
->method('hasParameterOption') ->method('hasParameterOption')
->with($this->equalTo('--no-cache')) ->with($this->equalTo('--no-cache'))
@ -83,12 +89,18 @@ class ApplicationTest extends TestCase
$inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock(); $inputMock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
$outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock(); $outputMock = $this->getMockBuilder('Symfony\Component\Console\Output\OutputInterface')->getMock();
putenv('COMPOSER_NO_INTERACTION=1');
$index = 0; $index = 0;
$inputMock->expects($this->at($index++)) $inputMock->expects($this->at($index++))
->method('hasParameterOption') ->method('hasParameterOption')
->with($this->equalTo('--no-plugins')) ->with($this->equalTo('--no-plugins'))
->will($this->returnValue(true)); ->will($this->returnValue(true));
$inputMock->expects($this->at($index++))
->method('setInteractive')
->with($this->equalTo(false));
$inputMock->expects($this->at($index++)) $inputMock->expects($this->at($index++))
->method('hasParameterOption') ->method('hasParameterOption')
->with($this->equalTo('--no-cache')) ->with($this->equalTo('--no-cache'))

View File

@ -548,6 +548,48 @@ class AutoloadGeneratorTest extends TestCase
); );
} }
public function testPSRToClassMapIgnoresNonPSRClasses()
{
$package = new Package('a', '1.0', '1.0');
$this->markTestSkipped('Skipped until ClassMapGenerator ignoring of invalid PSR-x classes is enabled');
$package->setAutoload(array(
'psr-0' => array('psr0_' => 'psr0/'),
'psr-4' => array('psr4\\' => 'psr4/'),
));
$this->repository->expects($this->once())
->method('getCanonicalPackages')
->will($this->returnValue(array()));
$this->fs->ensureDirectoryExists($this->workingDir.'/psr0/psr0');
$this->fs->ensureDirectoryExists($this->workingDir.'/psr4');
file_put_contents($this->workingDir.'/psr0/psr0/match.php', '<?php class psr0_match {}');
file_put_contents($this->workingDir.'/psr0/psr0/badfile.php', '<?php class psr0_badclass {}');
file_put_contents($this->workingDir.'/psr4/match.php', '<?php namespace psr4; class match {}');
file_put_contents($this->workingDir.'/psr4/badfile.php', '<?php namespace psr4; class badclass {}');
$this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_1');
$this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated.");
$expectedClassmap = <<<EOF
<?php
// autoload_classmap.php @generated by Composer
\$vendorDir = dirname(dirname(__FILE__));
\$baseDir = dirname(\$vendorDir);
return array(
'psr0_match' => \$baseDir . '/psr0/psr0/match.php',
'psr4\\\\match' => \$baseDir . '/psr4/match.php',
);
EOF;
$this->assertStringEqualsFile($this->vendorDir.'/composer/autoload_classmap.php', $expectedClassmap);
}
public function testVendorsClassMapAutoloading() public function testVendorsClassMapAutoloading()
{ {
$package = new Package('a', '1.0', '1.0'); $package = new Package('a', '1.0', '1.0');

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoloadOrder
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitFilesAutoload
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitIncludePath
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -13,6 +13,9 @@ class ComposerAutoloaderInitTargetDir
} }
} }
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader() public static function getLoader()
{ {
if (null !== self::$loader) { if (null !== self::$loader) {

View File

@ -1385,6 +1385,10 @@ namespace Foo;
<?php <?php
class LargeGap class LargeGap
{ {
public function a1380() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null)); } public function test_double_gap() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null));
?>
public function a1381() { var_dump(var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null), var_dump(null)); }
<?php
}
} }

View File

@ -112,7 +112,7 @@ class GitDownloaderTest extends TestCase
return 0; return 0;
})); }));
$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"); $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 && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
$processExecutor->expects($this->at(1)) $processExecutor->expects($this->at(1))
->method('execute') ->method('execute')
->with($this->equalTo($expectedGitCommand)) ->with($this->equalTo($expectedGitCommand))
@ -170,6 +170,9 @@ class GitDownloaderTest extends TestCase
$this->setupConfig($config); $this->setupConfig($config);
$cachePath = $config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', 'https://example.com/composer/composer').'/'; $cachePath = $config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', 'https://example.com/composer/composer').'/';
$filesystem = new \Composer\Util\Filesystem;
$filesystem->removeDirectory($cachePath);
$expectedGitCommand = $this->winCompat(sprintf("git clone --mirror 'https://example.com/composer/composer' '%s'", $cachePath)); $expectedGitCommand = $this->winCompat(sprintf("git clone --mirror 'https://example.com/composer/composer' '%s'", $cachePath));
$processExecutor->expects($this->at(1)) $processExecutor->expects($this->at(1))
->method('execute') ->method('execute')
@ -179,24 +182,36 @@ class GitDownloaderTest extends TestCase
return 0; return 0;
})); }));
$processExecutor->expects($this->at(2))
->method('execute')
->with($this->equalTo('git rev-parse --git-dir'), $this->anything(), $this->equalTo($this->winCompat($cachePath)))
->will($this->returnCallback(function ($command, &$output = null) {
$output = '.';
return 0;
}));
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($this->winCompat('git rev-parse --quiet --verify \'1234567890123456789012345678901234567890^{commit}\'')), $this->equalTo(null), $this->equalTo($this->winCompat($cachePath)))
->will($this->returnValue(0));
$expectedGitCommand = $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin 'https://example.com/composer/composer' && git remote add composer 'https://example.com/composer/composer'", $cachePath)); $expectedGitCommand = $this->winCompat(sprintf("git clone --no-checkout '%1\$s' 'composerPath' --dissociate --reference '%1\$s' && cd 'composerPath' && git remote set-url origin 'https://example.com/composer/composer' && git remote add composer 'https://example.com/composer/composer'", $cachePath));
$processExecutor->expects($this->at(2)) $processExecutor->expects($this->at(4))
->method('execute') ->method('execute')
->with($this->equalTo($expectedGitCommand)) ->with($this->equalTo($expectedGitCommand))
->will($this->returnValue(0)); ->will($this->returnValue(0));
$processExecutor->expects($this->at(3)) $processExecutor->expects($this->at(5))
->method('execute') ->method('execute')
->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath'))) ->with($this->equalTo($this->winCompat("git branch -r")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0)); ->will($this->returnValue(0));
$processExecutor->expects($this->at(4)) $processExecutor->expects($this->at(6))
->method('execute') ->method('execute')
->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath'))) ->with($this->equalTo($this->winCompat("git checkout 'master' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0)); ->will($this->returnValue(0));
$processExecutor->expects($this->at(5)) $processExecutor->expects($this->at(7))
->method('execute') ->method('execute')
->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath'))) ->with($this->equalTo($this->winCompat("git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0)); ->will($this->returnValue(0));
@ -235,7 +250,7 @@ class GitDownloaderTest extends TestCase
return 0; return 0;
})); }));
$expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer"); $expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://github.com/mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://github.com/mirrors/composer' && git fetch composer && git remote set-url origin 'https://github.com/mirrors/composer' && git remote set-url composer 'https://github.com/mirrors/composer'");
$processExecutor->expects($this->at(1)) $processExecutor->expects($this->at(1))
->method('execute') ->method('execute')
->with($this->equalTo($expectedGitCommand)) ->with($this->equalTo($expectedGitCommand))
@ -246,7 +261,7 @@ class GitDownloaderTest extends TestCase
->with() ->with()
->will($this->returnValue('Error1')); ->will($this->returnValue('Error1'));
$expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer"); $expectedGitCommand = $this->winCompat("git clone --no-checkout 'git@github.com:mirrors/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'git@github.com:mirrors/composer' && git fetch composer && git remote set-url origin 'git@github.com:mirrors/composer' && git remote set-url composer 'git@github.com:mirrors/composer'");
$processExecutor->expects($this->at(3)) $processExecutor->expects($this->at(3))
->method('execute') ->method('execute')
->with($this->equalTo($expectedGitCommand)) ->with($this->equalTo($expectedGitCommand))
@ -322,7 +337,7 @@ class GitDownloaderTest extends TestCase
return 0; return 0;
})); }));
$expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer"); $expectedGitCommand = $this->winCompat("git clone --no-checkout '{$url}' 'composerPath' && cd 'composerPath' && git remote add composer '{$url}' && git fetch composer && git remote set-url origin '{$url}' && git remote set-url composer '{$url}'");
$processExecutor->expects($this->at(1)) $processExecutor->expects($this->at(1))
->method('execute') ->method('execute')
->with($this->equalTo($expectedGitCommand)) ->with($this->equalTo($expectedGitCommand))
@ -350,7 +365,7 @@ class GitDownloaderTest extends TestCase
public function testDownloadThrowsRuntimeExceptionIfGitCommandFails() 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"); $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 && git remote set-url origin 'https://example.com/composer/composer' && git remote set-url composer 'https://example.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any()) $packageMock->expects($this->any())
->method('getSourceReference') ->method('getSourceReference')
@ -408,7 +423,7 @@ class GitDownloaderTest extends TestCase
public function testUpdate() public function testUpdate()
{ {
$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)"); $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); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any()) $packageMock->expects($this->any())
@ -440,7 +455,7 @@ class GitDownloaderTest extends TestCase
public function testUpdateWithNewRepoUrl() public function testUpdateWithNewRepoUrl()
{ {
$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)"); $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); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any()) $packageMock->expects($this->any())
@ -519,8 +534,8 @@ composer https://github.com/old/url (push)
*/ */
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails() 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)"); $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); git remote set-url composer 'https://github.com/composer/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)"); $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); git remote set-url composer 'git@github.com:composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any()) $packageMock->expects($this->any())
@ -563,8 +578,8 @@ composer https://github.com/old/url (push)
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover() public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
{ {
$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)"); $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); git remote set-url 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)"); $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); git remote set-url composer 'https://github.com/composer/composer'");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock(); $packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any()) $packageMock->expects($this->any())

View File

@ -0,0 +1,25 @@
--TEST--
Installs from composer.json without writing a lock file
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "a/a", "version": "1.0.0" }
]
}
],
"require": {
"a/a": "1.0.0"
},
"config": {
"lock": "false"
}
}
--RUN--
install
--EXPECT--
Installing a/a (1.0.0)
--EXPECT-LOCK--
false

View File

@ -0,0 +1,25 @@
--TEST--
Updates when no lock file is present without writing a lock file
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "a/a", "version": "1.0.0" }
]
}
],
"require": {
"a/a": "1.0.0"
},
"config": {
"lock": false
}
}
--RUN--
update
--EXPECT--
Installing a/a (1.0.0)
--EXPECT-LOCK--
false

View File

@ -243,6 +243,9 @@ class InstallerTest extends TestCase
// so store value temporarily in reference for later assetion // so store value temporarily in reference for later assetion
$actualLock = $hash; $actualLock = $hash;
})); }));
} elseif ($expectLock === false) {
$lockJsonMock->expects($this->never())
->method('write');
} }
$contents = json_encode($composerConfig); $contents = json_encode($composerConfig);
@ -334,6 +337,7 @@ class InstallerTest extends TestCase
continue; continue;
} }
try {
$testData = $this->readTestFile($file, $fixturesDir); $testData = $this->readTestFile($file, $fixturesDir);
$installed = array(); $installed = array();
@ -342,7 +346,6 @@ class InstallerTest extends TestCase
$expectLock = array(); $expectLock = array();
$expectResult = 0; $expectResult = 0;
try {
$message = $testData['TEST']; $message = $testData['TEST'];
$condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null; $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
$composer = JsonFile::parseJson($testData['COMPOSER']); $composer = JsonFile::parseJson($testData['COMPOSER']);
@ -373,8 +376,12 @@ class InstallerTest extends TestCase
} }
$run = $testData['RUN']; $run = $testData['RUN'];
if (!empty($testData['EXPECT-LOCK'])) { if (!empty($testData['EXPECT-LOCK'])) {
if ($testData['EXPECT-LOCK'] === 'false') {
$expectLock = false;
} else {
$expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']); $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
} }
}
$expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null; $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
$expect = $testData['EXPECT']; $expect = $testData['EXPECT'];
if (!empty($testData['EXPECT-EXCEPTION'])) { if (!empty($testData['EXPECT-EXCEPTION'])) {

View File

@ -19,6 +19,22 @@ use Composer\Package\Version\VersionParser;
class PathRepositoryTest extends TestCase class PathRepositoryTest extends TestCase
{ {
/**
* @expectedException RuntimeException
*/
public function testLoadPackageFromFileSystemWithIncorrectPath()
{
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
->getMock();
$config = new \Composer\Config();
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'missing'));
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
$repository->getPackages();
}
public function testLoadPackageFromFileSystemWithVersion() public function testLoadPackageFromFileSystemWithVersion()
{ {
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')

View File

@ -300,16 +300,16 @@ class FilesystemTest extends TestCase
// Create and detect junction // Create and detect junction
$fs->junction($target, $junction); $fs->junction($target, $junction);
$this->assertTrue($fs->isJunction($junction)); $this->assertTrue($fs->isJunction($junction), $junction . ': is a junction');
$this->assertFalse($fs->isJunction($target)); $this->assertFalse($fs->isJunction($target), $target . ': is not a junction');
$this->assertTrue($fs->isJunction($target . '/../../junction')); $this->assertTrue($fs->isJunction($target . '/../../junction'), $target . '/../../junction: is a junction');
$this->assertFalse($fs->isJunction($junction . '/../real')); $this->assertFalse($fs->isJunction($junction . '/../real'), $junction . '/../real: is not a junction');
$this->assertTrue($fs->isJunction($junction . '/../junction')); $this->assertTrue($fs->isJunction($junction . '/../junction'), $junction . '/../junction: is a junction');
// Remove junction // Remove junction
$this->assertTrue(is_dir($junction)); $this->assertTrue(is_dir($junction), $junction . ' is a directory');
$this->assertTrue($fs->removeJunction($junction)); $this->assertTrue($fs->removeJunction($junction), $junction . ' has been removed');
$this->assertFalse(is_dir($junction)); $this->assertFalse(is_dir($junction), $junction . ' is not a directory');
} }
public function testCopy() public function testCopy()