1
0
Fork 0

Merge remote-tracking branch 'upstream/master'

pull/9704/head
Dávid Andor 2021-02-16 21:33:27 +01:00
commit 25b2748101
70 changed files with 2087 additions and 318 deletions

View File

@ -52,5 +52,5 @@ jobs:
- name: Run PHPStan - name: Run PHPStan
# Locked to phpunit 7.5 here as newer ones have void return types which break inheritance # Locked to phpunit 7.5 here as newer ones have void return types which break inheritance
run: | run: |
bin/composer require --dev phpstan/phpstan:^0.12.42 phpunit/phpunit:^7.5.20 --with-all-dependencies ${{ env.COMPOSER_FLAGS }} bin/composer require --dev phpstan/phpstan:^0.12.69 phpunit/phpunit:^7.5.20 --with-all-dependencies ${{ env.COMPOSER_FLAGS }}
vendor/bin/phpstan analyse --configuration=phpstan/config.neon vendor/bin/phpstan analyse --configuration=phpstan/config.neon

View File

@ -1,3 +1,17 @@
### [2.0.9] 2021-01-27
* Added warning if the curl extension is not enabled as it significantly degrades performance
* Fixed InstalledVersions to report all packages when several vendor dirs are present in the same runtime
* Fixed download speed when downloading large files
* Fixed `archive` and path repo copies mishandling some .gitignore paths
* Fixed root package classes not being available to the plugins/scripts during the initial install
* Fixed cache writes to be atomic and better support multiple Composer processes running in parallel
* Fixed preg jit issues when `config` or `require` modifies large composer.json files
* Fixed compatibility with envs having open_basedir restrictions
* Fixed exclude-from-classmap causing regex issues when having too many paths
* Fixed compatibility issue with Symfony 4/5
* Several small performance and debug output improvements
### [2.0.8] 2020-12-03 ### [2.0.8] 2020-12-03
* Fixed packages with aliases not matching conflicts which match the alias * Fixed packages with aliases not matching conflicts which match the alias
@ -150,6 +164,11 @@
* Fixed suggest output being very spammy, it now is only one line long and shows more rarely * Fixed suggest output being very spammy, it now is only one line long and shows more rarely
* Fixed conflict rules like e.g. >=5 from matching dev-master, as it is not normalized to 9999999-dev internally anymore * Fixed conflict rules like e.g. >=5 from matching dev-master, as it is not normalized to 9999999-dev internally anymore
### [1.10.20] 2021-01-27
* Fixed exclude-from-classmap causing regex issues when having too many paths
* Fixed compatibility issue with Symfony 4/5
### [1.10.19] 2020-12-04 ### [1.10.19] 2020-12-04
* Fixed regression on PHP 8.0 * Fixed regression on PHP 8.0
@ -1076,6 +1095,7 @@
* Initial release * Initial release
[2.0.9]: https://github.com/composer/composer/compare/2.0.8...2.0.9
[2.0.8]: https://github.com/composer/composer/compare/2.0.7...2.0.8 [2.0.8]: https://github.com/composer/composer/compare/2.0.7...2.0.8
[2.0.7]: https://github.com/composer/composer/compare/2.0.6...2.0.7 [2.0.7]: https://github.com/composer/composer/compare/2.0.6...2.0.7
[2.0.6]: https://github.com/composer/composer/compare/2.0.5...2.0.6 [2.0.6]: https://github.com/composer/composer/compare/2.0.5...2.0.6
@ -1090,6 +1110,7 @@
[2.0.0-alpha3]: https://github.com/composer/composer/compare/2.0.0-alpha2...2.0.0-alpha3 [2.0.0-alpha3]: https://github.com/composer/composer/compare/2.0.0-alpha2...2.0.0-alpha3
[2.0.0-alpha2]: https://github.com/composer/composer/compare/2.0.0-alpha1...2.0.0-alpha2 [2.0.0-alpha2]: https://github.com/composer/composer/compare/2.0.0-alpha1...2.0.0-alpha2
[2.0.0-alpha1]: https://github.com/composer/composer/compare/1.10.7...2.0.0-alpha1 [2.0.0-alpha1]: https://github.com/composer/composer/compare/1.10.7...2.0.0-alpha1
[1.10.20]: https://github.com/composer/composer/compare/1.10.19...1.10.20
[1.10.19]: https://github.com/composer/composer/compare/1.10.18...1.10.19 [1.10.19]: https://github.com/composer/composer/compare/1.10.18...1.10.19
[1.10.18]: https://github.com/composer/composer/compare/1.10.17...1.10.18 [1.10.18]: https://github.com/composer/composer/compare/1.10.17...1.10.18
[1.10.17]: https://github.com/composer/composer/compare/1.10.16...1.10.17 [1.10.17]: https://github.com/composer/composer/compare/1.10.16...1.10.17

17
composer.lock generated
View File

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
"version": "1.2.8", "version": "1.2.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/ca-bundle.git", "url": "https://github.com/composer/ca-bundle.git",
"reference": "8a7ecad675253e4654ea05505233285377405215" "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/8a7ecad675253e4654ea05505233285377405215", "url": "https://api.github.com/repos/composer/ca-bundle/zipball/78a0e288fdcebf92aa2318a8d3656168da6ac1a5",
"reference": "8a7ecad675253e4654ea05505233285377405215", "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -26,14 +26,15 @@
"php": "^5.3.2 || ^7.0 || ^8.0" "php": "^5.3.2 || ^7.0 || ^8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "phpstan/phpstan": "^0.12.55",
"psr/log": "^1.0", "psr/log": "^1.0",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.x-dev" "dev-main": "1.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -63,7 +64,7 @@
"support": { "support": {
"irc": "irc://irc.freenode.org/composer", "irc": "irc://irc.freenode.org/composer",
"issues": "https://github.com/composer/ca-bundle/issues", "issues": "https://github.com/composer/ca-bundle/issues",
"source": "https://github.com/composer/ca-bundle/tree/1.2.8" "source": "https://github.com/composer/ca-bundle/tree/1.2.9"
}, },
"funding": [ "funding": [
{ {
@ -79,7 +80,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-08-23T12:54:47+00:00" "time": "2021-01-12T12:10:35+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",

View File

@ -943,6 +943,8 @@ The `COMPOSER_HOME` var allows you to change the Composer home directory. This
is a hidden, global (per-user on the machine) directory that is shared between is a hidden, global (per-user on the machine) directory that is shared between
all projects. all projects.
Use `composer config --global home` to see the location of the home directory.
By default, it points to `C:\Users\<user>\AppData\Roaming\Composer` on Windows By default, it points to `C:\Users\<user>\AppData\Roaming\Composer` on Windows
and `/Users/<user>/.composer` on macOS. On \*nix systems that follow the [XDG Base and `/Users/<user>/.composer` on macOS. On \*nix systems that follow the [XDG Base
Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), Directory Specifications](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html),
@ -1012,6 +1014,13 @@ similar use case), and need to support proxies, please provide the `CGI_HTTP_PRO
environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further environment variable instead. See [httpoxy.org](https://httpoxy.org/) for further
details. details.
### COMPOSER_MAX_PARALLEL_HTTP
Set to an integer to configure how many files can be downloaded in parallel. This
defaults to 12 and must be between 1 and 50. If your proxy has issues with
concurrency maybe you want to lower this. Increasing it should generally not result
in performance gains.
### HTTP_PROXY_REQUEST_FULLURI ### HTTP_PROXY_REQUEST_FULLURI
If you use a proxy, but it does not support the request_fulluri flag, then you If you use a proxy, but it does not support the request_fulluri flag, then you

View File

@ -401,19 +401,19 @@ Example:
#### require #### require
Lists packages required by this package. The package will not be installed Map of packages required by this package. The package will not be installed
unless those requirements can be met. unless those requirements can be met.
#### require-dev <span>([root-only](04-schema.md#root-package))</span> #### require-dev <span>([root-only](04-schema.md#root-package))</span>
Lists packages required for developing this package, or running Map of packages required for developing this package, or running
tests, etc. The dev requirements of the root package are installed by default. tests, etc. The dev requirements of the root package are installed by default.
Both `install` or `update` support the `--no-dev` option that prevents dev Both `install` or `update` support the `--no-dev` option that prevents dev
dependencies from being installed. dependencies from being installed.
#### conflict #### conflict
Lists packages that conflict with this version of this package. They Map of packages that conflict with this version of this package. They
will not be allowed to be installed together with your package. will not be allowed to be installed together with your package.
Note that when specifying ranges like `<1.0 >=1.1` in a `conflict` link, Note that when specifying ranges like `<1.0 >=1.1` in a `conflict` link,
@ -423,7 +423,7 @@ probably want to go for `<1.0 || >=1.1` in this case.
#### replace #### replace
Lists packages that are replaced by this package. This allows you to fork a Map of packages that are replaced by this package. This allows you to fork a
package, publish it under a different name with its own version numbers, while package, publish it under a different name with its own version numbers, while
packages requiring the original package continue to work with your fork because packages requiring the original package continue to work with your fork because
it replaces the original package. it replaces the original package.
@ -441,10 +441,12 @@ that exact version, and not any other version, which would be incorrect.
#### provide #### provide
List of other packages that are provided by this package. This is mostly Map of packages that are provided by this package. This is mostly
useful for implementations of common interfaces. A package could depend on useful for implementations of common interfaces. A package could depend on
some virtual `logger-implementation` package, any library that implements some virtual package e.g. `psr/logger-implementation`, any library that implements
this logger interface would list it in `provide`. this logger interface would list it in `provide`. Implementors can then
be [found on Packagist.org](https://packagist.org/providers/psr/log-implementation).
Using `provide` with the name of an actual package rather than a virtual one Using `provide` with the name of an actual package rather than a virtual one
implies that the code of that package is also shipped, in which case `replace` implies that the code of that package is also shipped, in which case `replace`
is generally a better choice. A common convention for packages providing an is generally a better choice. A common convention for packages providing an

View File

@ -576,14 +576,23 @@ the branch or tag that is currently checked out. Otherwise, the version should
be explicitly defined in the package's `composer.json` file. If the version be explicitly defined in the package's `composer.json` file. If the version
cannot be resolved by these means, it is assumed to be `dev-master`. cannot be resolved by these means, it is assumed to be `dev-master`.
When the version cannot be inferred from the local VCS repository, you should use When the version cannot be inferred from the local VCS repository, or when you
the special `branch-version` entry under `extra` instead of `version`: want to override the version, you can use the `versions` option when declaring
the repository:
```json ```json
{ {
"extra": { "repositories": [
"branch-version": "4.2-dev" {
} "type": "path",
"url": "../../packages/my-package",
"options": {
"versions": {
"my/package": "4.2-dev"
}
}
}
]
} }
``` ```

View File

@ -87,7 +87,7 @@ possible for safety.
---- ----
A few other methods are available for more complex usages, please refer to the A few other methods are available for more complex usages, please refer to the
source/docblocks of the class itself. source/docblocks of [the class itself](https://github.com/composer/composer/blob/master/src/Composer/InstalledVersions.php).
## Platform check ## Platform check

View File

@ -80,10 +80,9 @@ To fix this you need to open the file in an editor and fix the error. To find th
your global `auth.json`, execute: your global `auth.json`, execute:
```sh ```sh
composer config --global --list composer config --global home
``` ```
And look for the `[home]` section. (It is by default `~/.composer` or `%APPDATA%/Composer` on Windows)
The folder will contain your global `auth.json` if it exists. The folder will contain your global `auth.json` if it exists.
You can open this file in your favorite editor and fix the error. You can open this file in your favorite editor and fix the error.
@ -107,7 +106,7 @@ section or directly in the repository definition.
The final option to supply Composer with credentials is to use the `COMPOSER_AUTH` environment variable. The final option to supply Composer with credentials is to use the `COMPOSER_AUTH` environment variable.
These variables can be either passed as command line variables or set in actual environment variables. These variables can be either passed as command line variables or set in actual environment variables.
Read more about the usage of this environment variable [here](../03-cli.md#COMPOSER_AUTH). Read more about the usage of this environment variable [here](../03-cli.md#composer-auth).
# Authentication methods # Authentication methods

View File

@ -80,6 +80,19 @@ In the above example, if you wanted to check out the `my-feature` branch, you wo
When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix. When branch names look like versions, we have to clarify for composer that we're trying to check out a branch and not a tag. In the above example, we have two version branches: `v1` and `v2`. To get Composer to check out one of these branches, you must specify a version constraint that looks like this: `v1.x-dev`. The `.x` is an arbitrary string that Composer requires to tell it that we're talking about the `v1` branch and not a `v1` tag (alternatively, you can name the branch `v1.x` instead of `v1`). In the case of a branch with a version-like name (`v1`, in this case), you append `-dev` as a suffix, rather than using `dev-` as a prefix.
### Stabilities
Composer recognizes the following stabilities (in order of stability): dev,
alpha, beta, RC, and stable where RC stands for release candidate. The stability
of a version is defined by its suffix e.g version `v1.1-BETA` has a stability of
`beta` and `v1.1-RC1` has a stability of `RC`. If such a suffix is missing
e.g. version `v1.1` then Composer considers that version `stable`. In addition
to that Composer automatically adds a `-dev` suffix to all numeric branches and
prefixes all other branches imported from a VCS repository with `dev-`. In both
cases the stability `dev` gets assigned.
Keeping this in mind will help you in the next section.
### Minimum Stability ### Minimum Stability
There's one more thing that will affect which files are checked out of a library's VCS and added to your project: Composer allows you to specify stability constraints to limit which tags are considered valid. In the above example, note that the library released a beta and two release candidates for version `1.1` before the final official release. To receive these versions when running `composer install` or `composer update`, we have to explicitly tell Composer that we are ok with release candidates and beta releases (and alpha releases, if we want those). This can be done using either a project-wide `minimum-stability` value in `composer.json` or using "stability flags" in version constraints. Read more on the [schema page](../04-schema.md#minimum-stability). There's one more thing that will affect which files are checked out of a library's VCS and added to your project: Composer allows you to specify stability constraints to limit which tags are considered valid. In the above example, note that the library released a beta and two release candidates for version `1.1` before the final official release. To receive these versions when running `composer install` or `composer update`, we have to explicitly tell Composer that we are ok with release candidates and beta releases (and alpha releases, if we want those). This can be done using either a project-wide `minimum-stability` value in `composer.json` or using "stability flags" in version constraints. Read more on the [schema page](../04-schema.md#minimum-stability).

View File

@ -9,7 +9,7 @@ An alternative is to use this script which only works with UNIX utilities:
```bash ```bash
#!/bin/sh #!/bin/sh
EXPECTED_CHECKSUM="$(wget -q -O - https://composer.github.io/installer.sig)" EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"

View File

@ -275,7 +275,7 @@ EOF;
$excluded = null; $excluded = null;
if (!empty($autoloads['exclude-from-classmap'])) { if (!empty($autoloads['exclude-from-classmap'])) {
$excluded = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; $excluded = $autoloads['exclude-from-classmap'];
} }
$classMap = array(); $classMap = array();
@ -398,8 +398,31 @@ EOF;
return $classMap; return $classMap;
} }
/**
* @param ?array $excluded
*/
private function generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, $showAmbiguousWarning, array &$scannedFiles) private function generateClassMap($dir, $excluded, $namespaceFilter, $autoloadType, $showAmbiguousWarning, array &$scannedFiles)
{ {
if ($excluded) {
// filter excluded patterns here to only use those matching $dir
// exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
// if $dir does not exist, it should anyway not find anything there so no trouble
if (file_exists($dir)) {
// transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
$dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
foreach ($excluded as $index => $pattern) {
// extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
$pattern = preg_replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
// if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
if (0 !== strpos($pattern, $dirMatch) && 0 !== strpos($dirMatch, $pattern)) {
unset($excluded[$index]);
}
}
}
$excluded = $excluded ? '{(' . implode('|', $excluded) . ')}' : null;
}
return ClassMapGenerator::createMap($dir, $excluded, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles); return ClassMapGenerator::createMap($dir, $excluded, $showAmbiguousWarning ? $this->io : null, $namespaceFilter, $autoloadType, $scannedFiles);
} }
@ -513,7 +536,7 @@ EOF;
if (isset($autoloads['classmap'])) { if (isset($autoloads['classmap'])) {
$excluded = null; $excluded = null;
if (!empty($autoloads['exclude-from-classmap'])) { if (!empty($autoloads['exclude-from-classmap'])) {
$excluded = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}'; $excluded = $autoloads['exclude-from-classmap'];
} }
$scannedFiles = array(); $scannedFiles = array();
@ -838,7 +861,7 @@ PLATFORM_CHECK;
$file .= <<<CLASSLOADER_INIT $file .= <<<CLASSLOADER_INIT
spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader); spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader);
self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader(); self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader(\\dirname(\\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));

View File

@ -42,6 +42,8 @@ namespace Composer\Autoload;
*/ */
class ClassLoader class ClassLoader
{ {
private $vendorDir;
// PSR-4 // PSR-4
private $prefixLengthsPsr4 = array(); private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array(); private $prefixDirsPsr4 = array();
@ -57,6 +59,13 @@ class ClassLoader
private $missingClasses = array(); private $missingClasses = array();
private $apcuPrefix; private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes() public function getPrefixes()
{ {
if (!empty($this->prefixesPsr0)) { if (!empty($this->prefixesPsr0)) {
@ -300,6 +309,17 @@ class ClassLoader
public function register($prepend = false) public function register($prepend = false)
{ {
spl_autoload_register(array($this, 'loadClass'), true, $prepend); spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
} }
/** /**
@ -308,6 +328,10 @@ class ClassLoader
public function unregister() public function unregister()
{ {
spl_autoload_unregister(array($this, 'loadClass')); spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
} }
/** /**
@ -367,6 +391,16 @@ class ClassLoader
return $file; return $file;
} }
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext) private function findFileWithExtension($class, $ext)
{ {
// PSR-4 lookup // PSR-4 lookup

View File

@ -51,7 +51,7 @@ class ClassMapGenerator
* Iterate over all files in the given directory searching for classes * Iterate over all files in the given directory searching for classes
* *
* @param \Iterator|string $path The path to search in or an iterator * @param \Iterator|string $path The path to search in or an iterator
* @param string $excluded Regex that matches against the file path that exclude from the classmap. * @param string $excluded Regex that matches file paths to be excluded 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 * @param string $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules

View File

@ -114,20 +114,21 @@ class Cache
$this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG); $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);
$tempFileName = $this->root . $file . uniqid('.', true) . '.tmp';
try { try {
return file_put_contents($this->root . $file.'.tmp', $contents) !== false && rename($this->root . $file . '.tmp', $this->root . $file); return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file);
} catch (\ErrorException $e) { } catch (\ErrorException $e) {
$this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG); $this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) { if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
// Remove partial file. // Remove partial file.
unlink($this->root . $file); unlink($tempFileName);
$message = sprintf( $message = sprintf(
'<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available</warning>', '<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$u bytes of free space available</warning>',
$this->root . $file, $tempFileName,
$m[1], $m[1],
$m[2], $m[2],
@disk_free_space($this->root . dirname($file)) @disk_free_space(dirname($tempFileName))
); );
$this->io->writeError($message); $this->io->writeError($message);

View File

@ -15,6 +15,7 @@ namespace Composer\Command;
use Composer\Installer; use Composer\Installer;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Util\HttpDownloader;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@ -93,6 +94,10 @@ EOT
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
if ((!$composer->getLocker() || !$composer->getLocker()->isLocked()) && !HttpDownloader::isCurlEnabled()) {
$io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
}
$commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output); $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

View File

@ -126,7 +126,7 @@ EOT
$composeUser = posix_getpwuid(posix_geteuid()); $composeUser = posix_getpwuid(posix_geteuid());
$homeOwner = posix_getpwuid(fileowner($home)); $homeOwner = posix_getpwuid(fileowner($home));
if (isset($composeUser['name'], $homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) { if (isset($composeUser['name'], $homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) {
$io->writeError('<warning>You are running composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>'); $io->writeError('<warning>You are running Composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
} }
} }
@ -185,7 +185,7 @@ EOT
if (Composer::VERSION === $updateVersion) { if (Composer::VERSION === $updateVersion) {
$io->writeError( $io->writeError(
sprintf( sprintf(
'<info>You are already using composer version %s (%s channel).</info>', '<info>You are already using the latest available Composer version %s (%s channel).</info>',
$updateVersion, $updateVersion,
$channelString $channelString
) )

View File

@ -19,6 +19,7 @@ use Composer\IO\IOInterface;
use Composer\Plugin\CommandEvent; use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents; use Composer\Plugin\PluginEvents;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Util\HttpDownloader;
use Composer\Semver\Constraint\MultiConstraint; use Composer\Semver\Constraint\MultiConstraint;
use Composer\Package\Link; use Composer\Package\Link;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
@ -114,6 +115,10 @@ EOT
$composer = $this->getComposer(true, $input->getOption('no-plugins')); $composer = $this->getComposer(true, $input->getOption('no-plugins'));
if (!HttpDownloader::isCurlEnabled()) {
$io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
}
$packages = $input->getArgument('packages'); $packages = $input->getArgument('packages');
$reqs = $this->formatRequirements($input->getOption('with')); $reqs = $this->formatRequirements($input->getOption('with'));

View File

@ -78,8 +78,7 @@ class Config
public static $defaultRepositories = array( public static $defaultRepositories = array(
'packagist.org' => array( 'packagist.org' => array(
'type' => 'composer', 'type' => 'composer',
'url' => 'https?://repo.packagist.org', 'url' => 'https://repo.packagist.org',
'allow_ssl_downgrade' => true,
), ),
); );
@ -180,6 +179,11 @@ class Config
continue; continue;
} }
// auto-deactivate the default packagist.org repo if it gets redefined
if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && preg_match('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) {
$this->disableRepoByName('packagist.org');
}
// store repo // store repo
if (is_int($name)) { if (is_int($name)) {
$this->repositories[] = $repository; $this->repositories[] = $repository;

View File

@ -237,11 +237,11 @@ class Application extends BaseApplication
} }
if (extension_loaded('xdebug') && !getenv('COMPOSER_DISABLE_XDEBUG_WARN')) { if (extension_loaded('xdebug') && !getenv('COMPOSER_DISABLE_XDEBUG_WARN')) {
$io->writeError('<warning>You are running composer with Xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug</warning>'); $io->writeError('<warning>Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug</warning>');
} }
if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) { if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) {
$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 ( if (
@ -309,8 +309,9 @@ class Application extends BaseApplication
$result = parent::doRun($input, $output); $result = parent::doRun($input, $output);
// chdir back to $oldWorkingDir if set
if (isset($oldWorkingDir)) { if (isset($oldWorkingDir)) {
chdir($oldWorkingDir); Silencer::call('chdir', $oldWorkingDir);
} }
if (isset($startTime)) { if (isset($startTime)) {

View File

@ -63,7 +63,7 @@ class SolverProblemsException extends \RuntimeException
$hints[] = $this->createExtensionHint(); $hints[] = $this->createExtensionHint();
} }
if ($isCausedByLock && !$isDevExtraction) { if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) {
$hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions."; $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.";
} }

View File

@ -16,6 +16,7 @@ use Composer\Config;
use Composer\Cache; use Composer\Cache;
use Composer\IO\IOInterface; use Composer\IO\IOInterface;
use Composer\IO\NullIO; use Composer\IO\NullIO;
use Composer\Exception\IrrecoverableDownloadException;
use Composer\Package\Comparer\Comparer; use Composer\Package\Comparer\Comparer;
use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\InstallOperation;
@ -219,6 +220,10 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
} }
$self->clearLastCacheWrite($package); $self->clearLastCacheWrite($package);
if ($e instanceof IrrecoverableDownloadException) {
throw $e;
}
if ($e instanceof TransportException) { if ($e instanceof TransportException) {
// if we got an http response with a proper code, then requesting again will probably not help, abort // if we got an http response with a proper code, then requesting again will probably not help, abort
if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) { if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {

View File

@ -71,6 +71,8 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref) && is_dir($cachePath)) { if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref) && is_dir($cachePath)) {
$this->cachedPackages[$package->getId()][$ref] = true; $this->cachedPackages[$package->getId()][$ref] = true;
} }
} elseif (null === $gitVersion) {
throw new \RuntimeException('git was not found in your PATH, skipping source download');
} }
} }
@ -454,13 +456,10 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$command = sprintf('git checkout %s --', ProcessExecutor::escape($branch)); $command = sprintf('git checkout %s --', ProcessExecutor::escape($branch));
$fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch)); $fallbackCommand = sprintf('git checkout '.$force.'-B %s %s --', ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
if (0 === $this->process->execute($command, $output, $path) $resetCommand = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
|| 0 === $this->process->execute($fallbackCommand, $output, $path)
) { if (0 === $this->process->execute("($command || $fallbackCommand) && $resetCommand", $output, $path)) {
$command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference)); return null;
if (0 === $this->process->execute($command, $output, $path)) {
return null;
}
} }
} }

View File

@ -26,6 +26,9 @@ class HgDownloader extends VcsDownloader
*/ */
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{ {
if (null === HgUtils::getVersion($this->process)) {
throw new \RuntimeException('hg was not found in your PATH, skipping source download');
}
} }
/** /**

View File

@ -30,6 +30,11 @@ class SvnDownloader extends VcsDownloader
*/ */
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null) protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{ {
SvnUtil::cleanEnv();
$util = new SvnUtil($url, $this->io, $this->config, $this->process);
if (null === $util->binaryVersion()) {
throw new \RuntimeException('svn was not found in your PATH, skipping source download');
}
} }
/** /**

View File

@ -20,6 +20,7 @@ class TransportException extends \RuntimeException
protected $headers; protected $headers;
protected $response; protected $response;
protected $statusCode; protected $statusCode;
protected $responseInfo = array();
public function setHeaders($headers) public function setHeaders($headers)
{ {
@ -50,4 +51,20 @@ class TransportException extends \RuntimeException
{ {
return $this->statusCode; return $this->statusCode;
} }
/**
* @return array
*/
public function getResponseInfo()
{
return $this->responseInfo;
}
/**
* @param array $responseInfo
*/
public function setResponseInfo(array $responseInfo)
{
$this->responseInfo = $responseInfo;
}
} }

View File

@ -243,6 +243,8 @@ class EventDispatcher
if (strpos($exec, '@putenv ') === 0) { if (strpos($exec, '@putenv ') === 0) {
putenv(substr($exec, 8)); putenv(substr($exec, 8));
list($var, $value) = explode('=', substr($exec, 8), 2);
$_SERVER[$var] = $value;
continue; continue;
} }

View File

@ -12,6 +12,7 @@
namespace Composer; namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser; use Composer\Semver\VersionParser;
/** /**
@ -22,6 +23,8 @@ use Composer\Semver\VersionParser;
class InstalledVersions class InstalledVersions
{ {
private static $installed; private static $installed;
private static $canGetVendors;
private static $installedByVendor = array();
/** /**
* Returns a list of all package names which are present, either by being installed, replaced or provided * Returns a list of all package names which are present, either by being installed, replaced or provided
@ -31,7 +34,17 @@ class InstalledVersions
*/ */
public static function getInstalledPackages() public static function getInstalledPackages()
{ {
return array_keys(self::$installed['versions']); $packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
} }
/** /**
@ -44,7 +57,13 @@ class InstalledVersions
*/ */
public static function isInstalled($packageName) public static function isInstalled($packageName)
{ {
return isset(self::$installed['versions'][$packageName]); foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return true;
}
}
return false;
} }
/** /**
@ -79,25 +98,29 @@ class InstalledVersions
*/ */
public static function getVersionRanges($packageName) public static function getVersionRanges($packageName)
{ {
if (!isset(self::$installed['versions'][$packageName])) { foreach (self::getInstalled() as $installed) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
} }
$ranges = array(); throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
} }
/** /**
@ -106,15 +129,19 @@ class InstalledVersions
*/ */
public static function getVersion($packageName) public static function getVersion($packageName)
{ {
if (!isset(self::$installed['versions'][$packageName])) { foreach (self::getInstalled() as $installed) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
} }
if (!isset(self::$installed['versions'][$packageName]['version'])) { throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
return null;
}
return self::$installed['versions'][$packageName]['version'];
} }
/** /**
@ -123,15 +150,19 @@ class InstalledVersions
*/ */
public static function getPrettyVersion($packageName) public static function getPrettyVersion($packageName)
{ {
if (!isset(self::$installed['versions'][$packageName])) { foreach (self::getInstalled() as $installed) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
} }
if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
return null;
}
return self::$installed['versions'][$packageName]['pretty_version'];
} }
/** /**
@ -140,15 +171,19 @@ class InstalledVersions
*/ */
public static function getReference($packageName) public static function getReference($packageName)
{ {
if (!isset(self::$installed['versions'][$packageName])) { foreach (self::getInstalled() as $installed) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
} }
if (!isset(self::$installed['versions'][$packageName]['reference'])) { throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
return null;
}
return self::$installed['versions'][$packageName]['reference'];
} }
/** /**
@ -157,7 +192,9 @@ class InstalledVersions
*/ */
public static function getRootPackage() public static function getRootPackage()
{ {
return self::$installed['root']; $installed = self::getInstalled();
return $installed[0]['root'];
} }
/** /**
@ -192,5 +229,32 @@ class InstalledVersions
public static function reload($data) public static function reload($data)
{ {
self::$installed = $data; self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
}
}
}
$installed[] = self::$installed;
return $installed;
} }
} }

View File

@ -43,12 +43,14 @@ use Composer\Package\Version\VersionParser;
use Composer\Package\Package; use Composer\Package\Package;
use Composer\Repository\ArrayRepository; use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositorySet; use Composer\Repository\RepositorySet;
use Composer\Repository\CompositeRepository;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker; use Composer\Package\Locker;
use Composer\Package\RootPackageInterface; use Composer\Package\RootPackageInterface;
use Composer\Repository\InstalledArrayRepository; use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\InstalledRepository; use Composer\Repository\InstalledRepository;
use Composer\Repository\FilterRepository;
use Composer\Repository\RootPackageRepository; use Composer\Repository\RootPackageRepository;
use Composer\Repository\PlatformRepository; use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
@ -706,7 +708,7 @@ class Installer
return 0; return 0;
} }
private function createPlatformRepo($forUpdate) protected function createPlatformRepo($forUpdate)
{ {
if ($forUpdate) { if ($forUpdate) {
$platformOverrides = $this->config->get('platform') ?: array(); $platformOverrides = $this->config->get('platform') ?: array();
@ -766,6 +768,21 @@ class Installer
$repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage)); $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage));
$repositorySet->addRepository($platformRepo); $repositorySet->addRepository($platformRepo);
if ($this->additionalFixedRepository) { if ($this->additionalFixedRepository) {
// allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet
// see https://github.com/composer/composer/pull/9574
$additionalFixedRepositories = $this->additionalFixedRepository;
if ($additionalFixedRepositories instanceof CompositeRepository) {
$additionalFixedRepositories = $additionalFixedRepositories->getRepositories();
} else {
$additionalFixedRepositories = array($additionalFixedRepositories);
}
foreach ($additionalFixedRepositories as $additionalFixedRepository) {
if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) {
$repositorySet->allowInstalledRepositories();
break;
}
}
$repositorySet->addRepository($this->additionalFixedRepository); $repositorySet->addRepository($this->additionalFixedRepository);
} }

View File

@ -492,9 +492,15 @@ class InstallationManager
$promise = $installer->update($repo, $initial, $target); $promise = $installer->update($repo, $initial, $target);
$this->markForNotification($target); $this->markForNotification($target);
} else { } else {
$this->getInstaller($initialType)->uninstall($repo, $initial); $promise = $this->getInstaller($initialType)->uninstall($repo, $initial);
if (!$promise instanceof PromiseInterface) {
$promise = \React\Promise\resolve();
}
$installer = $this->getInstaller($targetType); $installer = $this->getInstaller($targetType);
$promise = $installer->install($repo, $target); $promise->then(function () use ($installer, $repo, $target) {
return $installer->install($repo, $target);
});
} }
return $promise; return $promise;

View File

@ -80,6 +80,7 @@ class JsonFile
/** /**
* Reads json file. * Reads json file.
* *
* @throws ParsingException
* @throws \RuntimeException * @throws \RuntimeException
* @return mixed * @return mixed
*/ */
@ -172,6 +173,7 @@ class JsonFile
* @param int $schema a JsonFile::*_SCHEMA constant * @param int $schema a JsonFile::*_SCHEMA constant
* @param string|null $schemaFile a path to the schema file * @param string|null $schemaFile a path to the schema file
* @throws JsonValidationException * @throws JsonValidationException
* @throws ParsingException
* @return bool true on success * @return bool true on success
*/ */
public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null) public function validateSchema($schema = self::STRICT_SCHEMA, $schemaFile = null)
@ -289,6 +291,7 @@ class JsonFile
* @param string $json json string * @param string $json json string
* @param string $file the json file * @param string $file the json file
* *
* @throws ParsingException
* @return mixed * @return mixed
*/ */
public static function parseJson($json, $file = null) public static function parseJson($json, $file = null)

View File

@ -20,13 +20,13 @@ use Composer\Repository\PlatformRepository;
class JsonManipulator class JsonManipulator
{ {
private static $DEFINES = '(?(DEFINE) private static $DEFINES = '(?(DEFINE)
(?<number> -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? ) (?<number> -? (?= [1-9]|0(?!\d) ) \d++ (\.\d++)? ([eE] [+-]?+ \d++)? )
(?<boolean> true | false | null ) (?<boolean> true | false | null )
(?<string> " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " ) (?<string> " ([^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " )
(?<array> \[ (?: (?&json) \s* (?: , (?&json) \s* )* )? \s* \] ) (?<array> \[ (?: (?&json) \s*+ (?: , (?&json) \s*+ )*+ )?+ \s*+ \] )
(?<pair> \s* (?&string) \s* : (?&json) \s* ) (?<pair> \s*+ (?&string) \s*+ : (?&json) \s*+ )
(?<object> \{ (?: (?&pair) (?: , (?&pair) )* )? \s* \} ) (?<object> \{ (?: (?&pair) (?: , (?&pair) )*+ )?+ \s*+ \} )
(?<json> \s* (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) ) (?<json> \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )
)'; )';
private $contents; private $contents;

View File

@ -123,26 +123,25 @@ abstract class BaseExcludeFilter
protected function generatePattern($rule) protected function generatePattern($rule)
{ {
$negate = false; $negate = false;
$pattern = '{'; $pattern = '';
if (strlen($rule) && $rule[0] === '!') { if ($rule !== '' && $rule[0] === '!') {
$negate = true; $negate = true;
$rule = substr($rule, 1); $rule = ltrim($rule, '!');
} }
if (strlen($rule) && $rule[0] === '/') { $firstSlashPosition = strpos($rule, '/');
$pattern .= '^/'; if (0 === $firstSlashPosition) {
$rule = substr($rule, 1); $pattern = '^/';
} elseif (strlen($rule) - 1 === strpos($rule, '/')) { } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) {
$pattern .= '/'; $pattern = '/';
$rule = substr($rule, 0, -1);
} elseif (false === strpos($rule, '/')) {
$pattern .= '/';
} }
$rule = trim($rule, '/');
// remove delimiters as well as caret (^) and dollar sign ($) from the regex // remove delimiters as well as caret (^) and dollar sign ($) from the regex
$pattern .= substr(Finder\Glob::toRegex($rule), 2, -2) . '(?=$|/)'; $rule = substr(Finder\Glob::toRegex($rule), 2, -2);
return array($pattern . '}', $negate, false); return array('{'.$pattern.$rule.'(?=$|/)}', $negate, false);
} }
} }

View File

@ -74,10 +74,8 @@ class RootPackageLoader extends ArrayLoader
if (!isset($config['version'])) { if (!isset($config['version'])) {
$commit = null; $commit = null;
if (isset($config['extra']['branch-version'])) { // override with env var if available
$config['version'] = preg_replace('{(\.x)?(-dev)?$}', '', $config['extra']['branch-version']).'.x-dev'; if (getenv('COMPOSER_ROOT_VERSION')) {
} elseif (getenv('COMPOSER_ROOT_VERSION')) {
// override with env var if available
$config['version'] = getenv('COMPOSER_ROOT_VERSION'); $config['version'] = getenv('COMPOSER_ROOT_VERSION');
} else { } else {
$versionData = $this->versionGuesser->guessVersion($config, $cwd ?: getcwd()); $versionData = $this->versionGuesser->guessVersion($config, $cwd ?: getcwd());

View File

@ -21,7 +21,9 @@ use Composer\Package\RootPackage;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryInterface; use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepository; use Composer\Repository\InstalledRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Package\PackageInterface; use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Package\Link; use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
use Composer\Plugin\Capability\Capability; use Composer\Plugin\Capability\Capability;
@ -174,7 +176,9 @@ class PluginManager
$localRepo = $this->composer->getRepositoryManager()->getLocalRepository(); $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
$globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null; $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
$installedRepo = new InstalledRepository(array($localRepo)); $rootPackage = clone $this->composer->getPackage();
$rootPackageRepo = new RootPackageRepository($rootPackage);
$installedRepo = new InstalledRepository(array($localRepo, $rootPackageRepo));
if ($globalRepo) { if ($globalRepo) {
$installedRepo->addRepository($globalRepo); $installedRepo->addRepository($globalRepo);
} }
@ -183,13 +187,17 @@ class PluginManager
$autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package); $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package);
$generator = $this->composer->getAutoloadGenerator(); $generator = $this->composer->getAutoloadGenerator();
$autoloads = array(); $autoloads = array(array($rootPackage, ''));
foreach ($autoloadPackages as $autoloadPackage) { foreach ($autoloadPackages as $autoloadPackage) {
if ($autoloadPackage === $rootPackage) {
continue;
}
$downloadPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage)); $downloadPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage));
$autoloads[] = array($autoloadPackage, $downloadPath); $autoloads[] = array($autoloadPackage, $downloadPath);
} }
$map = $generator->parseAutoloads($autoloads, new RootPackage('dummy/root-package', '1.0.0.0', '1.0.0')); $map = $generator->parseAutoloads($autoloads, $rootPackage);
$classLoader = $generator->createLoader($map); $classLoader = $generator->createLoader($map);
$classLoader->register(); $classLoader->register();
@ -403,12 +411,7 @@ class PluginManager
*/ */
private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package) private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package)
{ {
$requires = array_merge( foreach ($package->getRequires() as $requireLink) {
$package->getRequires(),
$package->getDevRequires()
);
foreach ($requires as $requireLink) {
foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) { foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) {
if (!isset($collected[$requiredPackage->getName()])) { if (!isset($collected[$requiredPackage->getName()])) {
$collected[$requiredPackage->getName()] = $requiredPackage; $collected[$requiredPackage->getName()] = $requiredPackage;

View File

@ -532,6 +532,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
*/ */
private function whatProvides($name, array $acceptableStabilities = null, array $stabilityFlags = null, array $alreadyLoaded = array()) private function whatProvides($name, array $acceptableStabilities = null, array $stabilityFlags = null, array $alreadyLoaded = array())
{ {
$packagesSource = null;
if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) { if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) {
// skip platform packages, root package and composer-plugin-api // skip platform packages, root package and composer-plugin-api
if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) { if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) {
@ -565,15 +566,18 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
if ($cacheKey) { if ($cacheKey) {
if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) { if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) {
$packages = json_decode($this->cache->read($cacheKey), true); $packages = json_decode($this->cache->read($cacheKey), true);
$packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
} elseif ($useLastModifiedCheck) { } elseif ($useLastModifiedCheck) {
if ($contents = $this->cache->read($cacheKey)) { if ($contents = $this->cache->read($cacheKey)) {
$contents = json_decode($contents, true); $contents = json_decode($contents, true);
// we already loaded some packages from this file, so assume it is fresh and avoid fetching it again // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again
if (isset($alreadyLoaded[$name])) { if (isset($alreadyLoaded[$name])) {
$packages = $contents; $packages = $contents;
$packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
} elseif (isset($contents['last-modified'])) { } elseif (isset($contents['last-modified'])) {
$response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']); $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']);
$packages = true === $response ? $contents : $response; $packages = true === $response ? $contents : $response;
$packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')';
} }
} }
} }
@ -582,10 +586,12 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
if (!$packages) { if (!$packages) {
try { try {
$packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck); $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck);
$packagesSource = 'downloaded file ('.Url::sanitize($url).')';
} catch (TransportException $e) { } catch (TransportException $e) {
// 404s are acceptable for lazy provider repos // 404s are acceptable for lazy provider repos
if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), array(404, 499), true)) { if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), array(404, 499), true)) {
$packages = array('packages' => array()); $packages = array('packages' => array());
$packagesSource = 'not-found file ('.Url::sanitize($url).')';
if ($e->getStatusCode() === 499) { if ($e->getStatusCode() === 499) {
$this->io->error('<warning>' . $e->getMessage() . '</warning>'); $this->io->error('<warning>' . $e->getMessage() . '</warning>');
} }
@ -598,6 +604,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$loadingPartialPackage = false; $loadingPartialPackage = false;
} else { } else {
$packages = array('packages' => array('versions' => $this->partialPackagesByName[$name])); $packages = array('packages' => array('versions' => $this->partialPackagesByName[$name]));
$packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')';
$loadingPartialPackage = true; $loadingPartialPackage = true;
} }
@ -637,7 +644,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
// load acceptable packages in the providers // load acceptable packages in the providers
$loadedPackages = $this->createPackages($versionsToLoad); $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
$uids = array_keys($versionsToLoad); $uids = array_keys($versionsToLoad);
foreach ($loadedPackages as $index => $package) { foreach ($loadedPackages as $index => $package) {
@ -667,7 +674,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
$repoData = $this->loadDataFromServer(); $repoData = $this->loadDataFromServer();
foreach ($this->createPackages($repoData) as $package) { foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) {
$this->addPackage($package); $this->addPackage($package);
} }
} }
@ -729,8 +736,11 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
$promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified) $promises[] = $this->asyncFetchFile($url, $cacheKey, $lastModified)
->then(function ($response) use (&$packages, &$namesFound, $contents, $realName, $constraint, $repo, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) { ->then(function ($response) use (&$packages, &$namesFound, $url, $cacheKey, $contents, $realName, $constraint, $repo, $acceptableStabilities, $stabilityFlags, $alreadyLoaded) {
$packagesSource = 'downloaded file ('.Url::sanitize($url).')';
if (true === $response) { if (true === $response) {
$packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
$response = $contents; $response = $contents;
} }
@ -764,7 +774,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
} }
$loadedPackages = $repo->createPackages($versionsToLoad); $loadedPackages = $repo->createPackages($versionsToLoad, $packagesSource);
foreach ($loadedPackages as $package) { foreach ($loadedPackages as $package) {
$package->setRepository($repo); $package->setRepository($repo);
$packages[spl_object_hash($package)] = $package; $packages[spl_object_hash($package)] = $package;
@ -812,6 +822,17 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return false; return false;
} }
private function getPackagesJsonUrl()
{
$jsonUrlParts = parse_url($this->url);
if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
return $this->url;
}
return $this->url . '/packages.json';
}
protected function loadRootServerFile() protected function loadRootServerFile()
{ {
if (null !== $this->rootData) { if (null !== $this->rootData) {
@ -822,15 +843,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url); throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
} }
$jsonUrlParts = parse_url($this->url); $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json');
if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
$jsonUrl = $this->url;
} else {
$jsonUrl = $this->url . '/packages.json';
}
$data = $this->fetchFile($jsonUrl, 'packages.json');
if (!empty($data['notify-batch'])) { if (!empty($data['notify-batch'])) {
$this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']); $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']);
@ -1027,7 +1040,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
* *
* @private * @private
*/ */
public function createPackages(array $packages, $class = 'Composer\Package\CompletePackage') public function createPackages(array $packages, $source = null)
{ {
if (!$packages) { if (!$packages) {
return array(); return array();
@ -1040,7 +1053,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
} }
} }
$packages = $this->loader->loadPackages($packages, $class); $packages = $this->loader->loadPackages($packages, 'Composer\Package\CompletePackage');
foreach ($packages as $package) { foreach ($packages as $package) {
if (isset($this->sourceMirrors[$package->getSourceType()])) { if (isset($this->sourceMirrors[$package->getSourceType()])) {
@ -1052,7 +1065,7 @@ class ComposerRepository extends ArrayRepository implements ConfigurableReposito
return $packages; return $packages;
} catch (\Exception $e) { } catch (\Exception $e) {
throw new \RuntimeException('Could not load packages '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e); throw new \RuntimeException('Could not load packages '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
} }
} }

View File

@ -204,6 +204,9 @@ class FilesystemRepository extends WritableArrayRepository
$fs->filePutContentsIfModified($repoDir.'/installed.php', '<?php return '.var_export($versions, true).';'."\n"); $fs->filePutContentsIfModified($repoDir.'/installed.php', '<?php return '.var_export($versions, true).';'."\n");
$installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php'); $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php');
// while not strictly needed since https://github.com/composer/composer/pull/9635 - we keep this for BC
// and overall broader compatibility with people that may not use Composer's ClassLoader. They can
// simply include InstalledVersions.php manually and have it working in a basic way.
$installedVersionsClass = str_replace('private static $installed;', 'private static $installed = '.var_export($versions, true).';', $installedVersionsClass); $installedVersionsClass = str_replace('private static $installed;', 'private static $installed = '.var_export($versions, true).';', $installedVersionsClass);
$fs->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass); $fs->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass);

View File

@ -170,10 +170,11 @@ class PathRepository extends ArrayRepository implements ConfigurableRepositoryIn
'reference' => sha1($json . serialize($this->options)), 'reference' => sha1($json . serialize($this->options)),
); );
$package['transport-options'] = $this->options; $package['transport-options'] = $this->options;
unset($package['transport-options']['versions']);
// use the branch-version as the package version if available // use the version provided as option if available
if (!isset($package['version']) && isset($package['extra']['branch-version'])) { if (isset($package['name'], $this->options['versions'][$package['name']])) {
$package['version'] = preg_replace('{(\.x)?(-dev)?$}', '', $package['extra']['branch-version']).'.x-dev'; $package['version'] = $this->options['versions'][$package['name']];
} }
// carry over the root package version if this path repo is in the same git repository as root package // carry over the root package version if this path repo is in the same git repository as root package

View File

@ -37,6 +37,9 @@ class GitDriver extends VcsDriver
{ {
if (Filesystem::isLocalPath($this->url)) { if (Filesystem::isLocalPath($this->url)) {
$this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url); $this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url);
if (!is_dir($this->url)) {
throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist');
}
$this->repoDir = $this->url; $this->repoDir = $this->url;
$cacheUrl = realpath($this->url); $cacheUrl = realpath($this->url);
} else { } else {

View File

@ -63,7 +63,7 @@ class Filesystem
public function emptyDirectory($dir, $ensureDirectoryExists = true) public function emptyDirectory($dir, $ensureDirectoryExists = true)
{ {
if (file_exists($dir) && is_link($dir)) { if (is_link($dir) && file_exists($dir)) {
$this->unlink($dir); $this->unlink($dir);
} }
@ -108,7 +108,7 @@ class Filesystem
return unlink($directory); return unlink($directory);
} }
if (!file_exists($directory) || !is_dir($directory)) { if (!is_dir($directory) || !file_exists($directory)) {
return true; return true;
} }
@ -131,7 +131,7 @@ class Filesystem
// clear stat cache because external processes aren't tracked by the php stat cache // clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache(); clearstatcache();
if ($result && !file_exists($directory)) { if ($result && !is_dir($directory)) {
return true; return true;
} }
@ -600,7 +600,7 @@ class Filesystem
if (!function_exists('symlink')) { if (!function_exists('symlink')) {
return false; return false;
} }
$cwd = getcwd(); $cwd = getcwd();
$relativePath = $this->findShortestPath($link, $target); $relativePath = $this->findShortestPath($link, $target);

View File

@ -101,7 +101,7 @@ class Git
$errorMsg = $this->process->getErrorOutput(); $errorMsg = $this->process->getErrorOutput();
// private github repository without ssh key 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) || preg_match('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $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);
@ -122,7 +122,7 @@ class Git
$errorMsg = $this->process->getErrorOutput(); $errorMsg = $this->process->getErrorOutput();
} }
} elseif (preg_match('{^https://(bitbucket\.org)/(.*)(\.git)?$}U', $url, $match)) { //bitbucket oauth } elseif (preg_match('{^https://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)) { //bitbucket oauth
$bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process); $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process);
if (!$this->io->hasAuthentication($match[1])) { if (!$this->io->hasAuthentication($match[1])) {
@ -167,7 +167,7 @@ class Git
} }
} elseif ( } elseif (
preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match) preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match)
|| preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match) || preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match)
) { ) {
if ($match[1] === 'git') { if ($match[1] === 'git') {
$match[1] = 'https'; $match[1] = 'https';
@ -354,7 +354,7 @@ class Git
// added in git 1.7.1, prevents prompting the user for username/password // added in git 1.7.1, prevents prompting the user for username/password
if (getenv('GIT_ASKPASS') !== 'echo') { if (getenv('GIT_ASKPASS') !== 'echo') {
putenv('GIT_ASKPASS=echo'); putenv('GIT_ASKPASS=echo');
unset($_SERVER['GIT_ASKPASS']); $_SERVER['GIT_ASKPASS'] = 'echo';
} }
// clean up rogue git env vars in case this is running in a git hook // clean up rogue git env vars in case this is running in a git hook
@ -370,6 +370,7 @@ class Git
// Run processes with predictable LANGUAGE // Run processes with predictable LANGUAGE
if (getenv('LANGUAGE') !== 'C') { if (getenv('LANGUAGE') !== 'C') {
putenv('LANGUAGE=C'); putenv('LANGUAGE=C');
$_SERVER['LANGUAGE'] = 'C';
} }
// clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940 // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
@ -402,15 +403,12 @@ class Git
/** /**
* Retrieves the current git version. * Retrieves the current git version.
* *
* @return string|null The git version number. * @return string|null The git version number, if present.
*/ */
public static function getVersion(ProcessExecutor $process) public static function getVersion(ProcessExecutor $process)
{ {
if (false === self::$version) { if (false === self::$version) {
self::$version = null; self::$version = null;
if (!$process) {
$process = new ProcessExecutor;
}
if (0 === $process->execute('git --version', $output) && preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) { if (0 === $process->execute('git --version', $output) && preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
self::$version = $matches[1]; self::$version = $matches[1];
} }

View File

@ -20,6 +20,8 @@ use Composer\IO\IOInterface;
*/ */
class Hg class Hg
{ {
private static $version = false;
/** /**
* @var \Composer\IO\IOInterface * @var \Composer\IO\IOInterface
*/ */
@ -74,10 +76,27 @@ class Hg
private function throwException($message, $url) private function throwException($message, $url)
{ {
if (0 !== $this->process->execute('hg --version', $ignoredOutput)) { if (null === self::getVersion($this->process)) {
throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput())); throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
} }
throw new \RuntimeException(Url::sanitize($message)); throw new \RuntimeException(Url::sanitize($message));
} }
/**
* Retrieves the current hg version.
*
* @return string|null The hg version number, if present.
*/
public static function getVersion(ProcessExecutor $process)
{
if (false === self::$version) {
self::$version = null;
if (0 === $process->execute('hg --version', $output) && preg_match('/version (\d+(?:\.\d+)+)/m', $output, $matches)) {
self::$version = $matches[1];
}
}
return self::$version;
}
} }

View File

@ -321,12 +321,12 @@ class CurlDownloader
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 CurlResponse(array('url' => $progress['url']), $statusCode, $headers, $contents, $progress);
$this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($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 CurlResponse(array('url' => $progress['url']), $statusCode, $headers, $contents, $progress);
$this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG); $this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), true, IOInterface::DEBUG);
} }
fclose($job['bodyHandle']); fclose($job['bodyHandle']);
@ -374,6 +374,9 @@ class CurlDownloader
if ($e instanceof TransportException && $response) { if ($e instanceof TransportException && $response) {
$e->setResponse($response->getBody()); $e->setResponse($response->getBody());
} }
if ($e instanceof TransportException && $progress) {
$e->setResponseInfo($progress);
}
if (is_resource($job['headerHandle'])) { if (is_resource($job['headerHandle'])) {
fclose($job['headerHandle']); fclose($job['headerHandle']);
@ -511,7 +514,12 @@ class CurlDownloader
@unlink($job['filename'].'~'); @unlink($job['filename'].'~');
} }
return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')', $response->getStatusCode()); $details = '';
if ($response->getHeader('content-type') === 'application/json') {
$details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : '');
}
return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode());
} }
private function checkCurlResult($code) private function checkCurlResult($code)

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Util\Http;
class CurlResponse extends Response
{
private $curlInfo;
public function __construct(array $request, $code, array $headers, $body, array $curlInfo)
{
parent::__construct($request, $code, $headers, $body);
$this->curlInfo = $curlInfo;
}
/**
* @return array
*/
public function getCurlInfo()
{
return $this->curlInfo;
}
}

View File

@ -87,7 +87,7 @@ class ProxyManager
$options = array(); $options = array();
$formattedProxyUrl = ''; $formattedProxyUrl = '';
if ($this->hasProxy && $this->fullProxy[$scheme]) { if ($this->hasProxy && in_array($scheme, array('http', 'https'), true) && $this->fullProxy[$scheme]) {
if ($this->noProxy($requestUrl)) { if ($this->noProxy($requestUrl)) {
$formattedProxyUrl = 'excluded by no_proxy'; $formattedProxyUrl = 'excluded by no_proxy';
} else { } else {

View File

@ -19,6 +19,7 @@ use Composer\Util\Http\Response;
use Composer\Composer; use Composer\Composer;
use Composer\Package\Version\VersionParser; use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint; use Composer\Semver\Constraint\Constraint;
use Composer\Exception\IrrecoverableDownloadException;
use React\Promise\Promise; use React\Promise\Promise;
/** /**
@ -37,7 +38,7 @@ class HttpDownloader
private $jobs = array(); private $jobs = array();
private $options = array(); private $options = array();
private $runningJobs = 0; private $runningJobs = 0;
private $maxJobs = 10; private $maxJobs = 12;
private $curl; private $curl;
private $rfs; private $rfs;
private $idGen = 0; private $idGen = 0;
@ -71,6 +72,10 @@ class HttpDownloader
} }
$this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls); $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls);
if (is_numeric($maxJobs = getenv('COMPOSER_MAX_PARALLEL_HTTP'))) {
$this->maxJobs = max(1, min(50, (int) $maxJobs));
}
} }
/** /**
@ -253,10 +258,11 @@ class HttpDownloader
if (isset($job['curl_id'])) { if (isset($job['curl_id'])) {
$curl->abortRequest($job['curl_id']); $curl->abortRequest($job['curl_id']);
} }
throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled');
}; };
$promise = new Promise($resolver, $canceler); $promise = new Promise($resolver, $canceler);
$promise->then(function ($response) use (&$job, $downloader) { $promise = $promise->then(function ($response) use (&$job, $downloader) {
$job['status'] = HttpDownloader::STATUS_COMPLETED; $job['status'] = HttpDownloader::STATUS_COMPLETED;
$job['response'] = $response; $job['response'] = $response;
@ -302,7 +308,7 @@ class HttpDownloader
if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) { if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) {
$resolve(new Response(array('url' => $url), 304, array(), '')); $resolve(new Response(array('url' => $url), 304, array(), ''));
} else { } else {
$e = new TransportException('Network disabled, request canceled: '.$url, 499); $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499);
$e->setStatusCode(499); $e->setStatusCode(499);
$reject($e); $reject($e);
} }
@ -336,13 +342,9 @@ class HttpDownloader
*/ */
public function wait($index = null) public function wait($index = null)
{ {
while (true) { do {
if (!$this->countActiveJobs($index)) { $jobCount = $this->countActiveJobs($index);
return; } while ($jobCount);
}
usleep(1000);
}
} }
/** /**
@ -429,7 +431,7 @@ class HttpDownloader
} }
} }
$io->writeError('<'.$type.'>'.ucfirst($type).' from '.$url.': '.$data[$type].'</'.$type.'>'); $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$data[$type].'</'.$type.'>');
} }
} }

View File

@ -85,6 +85,7 @@ class Loop
$progress->start($totalJobs); $progress->start($totalJobs);
} }
$lastUpdate = 0;
while (true) { while (true) {
$activeJobs = 0; $activeJobs = 0;
@ -95,15 +96,19 @@ class Loop
$activeJobs += $this->processExecutor->countActiveJobs(); $activeJobs += $this->processExecutor->countActiveJobs();
} }
if ($progress) { if ($progress && microtime(true) - $lastUpdate > 0.1) {
$lastUpdate = microtime(true);
$progress->setProgress($progress->getMaxSteps() - $activeJobs); $progress->setProgress($progress->getMaxSteps() - $activeJobs);
} }
if (!$activeJobs) { if (!$activeJobs) {
break; break;
} }
}
usleep(5000); // as we skip progress updates if they are too quick, make sure we do one last one here at 100%
if ($progress) {
$progress->setProgress($progress->getMaxSteps());
} }
unset($this->currentPromises[$waitIndex]); unset($this->currentPromises[$waitIndex]);

View File

@ -98,11 +98,15 @@ class ProcessExecutor
$this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand); $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
} }
// TODO in 2.2, these two checks can be dropped as Symfony 4+ supports them out of the box
// make sure that null translate to the proper directory in case the dir is a symlink // make sure that null translate to the proper directory in case the dir is a symlink
// and we call a git command, because msysgit does not handle symlinks properly // and we call a git command, because msysgit does not handle symlinks properly
if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) { if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) {
$cwd = realpath(getcwd()); $cwd = realpath(getcwd());
} }
if (null !== $cwd && !is_dir($cwd)) {
throw new \RuntimeException('The given CWD for the process does not exist: '.$cwd);
}
$this->captureOutput = func_num_args() > 3; $this->captureOutput = func_num_args() > 3;
$this->errorOutput = null; $this->errorOutput = null;
@ -177,6 +181,8 @@ class ProcessExecutor
// signal can throw in various conditions, but we don't care if it fails // signal can throw in various conditions, but we don't care if it fails
} }
$job['process']->stop(1); $job['process']->stop(1);
throw new \RuntimeException('Aborted process');
}; };
$promise = new Promise($resolver, $canceler); $promise = new Promise($resolver, $canceler);
@ -233,11 +239,15 @@ class ProcessExecutor
$this->io->writeError('Executing async command ('.($cwd ?: 'CWD').'): '.$safeCommand); $this->io->writeError('Executing async command ('.($cwd ?: 'CWD').'): '.$safeCommand);
} }
// TODO in 2.2, these two checks can be dropped as Symfony 4+ supports them out of the box
// make sure that null translate to the proper directory in case the dir is a symlink // make sure that null translate to the proper directory in case the dir is a symlink
// and we call a git command, because msysgit does not handle symlinks properly // and we call a git command, because msysgit does not handle symlinks properly
if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) { if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) {
$cwd = realpath(getcwd()); $cwd = realpath(getcwd());
} }
if (null !== $cwd && !is_dir($cwd)) {
throw new \RuntimeException('The given CWD for the process does not exist: '.$cwd);
}
// TODO in v3, commands should be passed in as arrays of cmd + args // TODO in v3, commands should be passed in as arrays of cmd + args
if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {

View File

@ -85,7 +85,7 @@ class AllFunctionalTest extends TestCase
} }
} }
$proc = new Process('php '.escapeshellarg('./bin/compile'), $target); $proc = new Process('php -dphar.readonly=0 '.escapeshellarg('./bin/compile'), $target);
$exitcode = $proc->run(); $exitcode = $proc->run();
if ($exitcode !== 0 || trim($proc->getOutput())) { if ($exitcode !== 0 || trim($proc->getOutput())) {
@ -102,26 +102,73 @@ class AllFunctionalTest extends TestCase
public function testIntegration($testFile) public function testIntegration($testFile)
{ {
$testData = $this->parseTestFile($testFile); $testData = $this->parseTestFile($testFile);
$this->testDir = self::getUniqueTmpDirectory();
// if a dir is present with the name of the .test file (without .test), we
// copy all its contents in the $testDir to be used to run the test with
$testFileSetupDir = substr($testFile, 0, -5);
if (is_dir($testFileSetupDir)) {
$fs = new Filesystem();
$fs->copy($testFileSetupDir, $this->testDir);
}
$this->oldenv = getenv('COMPOSER_HOME'); $this->oldenv = getenv('COMPOSER_HOME');
$_SERVER['COMPOSER_HOME'] = $this->testDir.'home'; $_SERVER['COMPOSER_HOME'] = $this->testDir.'home';
putenv('COMPOSER_HOME='.$_SERVER['COMPOSER_HOME']); putenv('COMPOSER_HOME='.$_SERVER['COMPOSER_HOME']);
$cmd = 'php '.escapeshellarg(self::$pharPath).' --no-ansi '.$testData['RUN']; $cmd = 'php '.escapeshellarg(self::$pharPath).' --no-ansi '.$testData['RUN'];
$proc = new Process($cmd, __DIR__.'/Fixtures/functional', null, null, 300); $proc = new Process($cmd, $this->testDir, null, null, 300);
$exitcode = $proc->run(); $output = '';
$exitcode = $proc->run(function ($type, $buffer) use (&$output) {
$output .= $buffer;
});
if (isset($testData['EXPECT'])) { if (isset($testData['EXPECT'])) {
$this->assertEquals($testData['EXPECT'], $this->cleanOutput($proc->getOutput()), 'Error Output: '.$proc->getErrorOutput()); $output = trim($this->cleanOutput($output));
$expected = $testData['EXPECT'];
$line = 1;
for ($i = 0, $j = 0; $i < strlen($expected); ) {
if ($expected[$i] === "\n") {
$line++;
}
if ($expected[$i] === '%') {
preg_match('{%(.+?)%}', substr($expected, $i), $match);
$regex = $match[1];
if (preg_match('{'.$regex.'}', substr($output, $j), $match)) {
$i += strlen($regex) + 2;
$j += strlen($match[0]);
continue;
} else {
$this->fail(
'Failed to match pattern '.$regex.' at line '.$line.' / abs offset '.$i.': '
.substr($output, $j, min(strpos($output, "\n", $j)-$j, 100)).PHP_EOL.PHP_EOL.
'Output:'.PHP_EOL.$output
);
}
}
if ($expected[$i] !== $output[$j]) {
$this->fail(
'Output does not match expectation at line '.$line.' / abs offset '.$i.': '.PHP_EOL
.'-'.substr($expected, $i, min(strpos($expected, "\n", $i)-$i, 100)).PHP_EOL
.'+'.substr($output, $j, min(strpos($output, "\n", $j)-$j, 100)).PHP_EOL.PHP_EOL
.'Output:'.PHP_EOL.$output
);
}
$i++;
$j++;
}
} }
if (isset($testData['EXPECT-REGEX'])) { if (isset($testData['EXPECT-REGEX'])) {
$this->assertRegExp($testData['EXPECT-REGEX'], $this->cleanOutput($proc->getOutput()), 'Error Output: '.$proc->getErrorOutput()); $this->assertRegExp($testData['EXPECT-REGEX'], $this->cleanOutput($output));
} }
if (isset($testData['EXPECT-ERROR'])) { if (isset($testData['EXPECT-REGEXES'])) {
$this->assertEquals($testData['EXPECT-ERROR'], $this->cleanOutput($proc->getErrorOutput())); $cleanOutput = $this->cleanOutput($output);
} foreach (explode("\n", $testData['EXPECT-REGEXES']) as $regex) {
if (isset($testData['EXPECT-ERROR-REGEX'])) { $this->assertRegExp($regex, $cleanOutput, 'Output: '.$output);
$this->assertRegExp($testData['EXPECT-ERROR-REGEX'], $this->cleanOutput($proc->getErrorOutput())); }
} }
if (isset($testData['EXPECT-EXIT-CODE'])) { if (isset($testData['EXPECT-EXIT-CODE'])) {
$this->assertSame($testData['EXPECT-EXIT-CODE'], $exitcode); $this->assertSame($testData['EXPECT-EXIT-CODE'], $exitcode);
@ -132,7 +179,7 @@ class AllFunctionalTest extends TestCase
{ {
$tests = array(); $tests = array();
foreach (Finder::create()->in(__DIR__.'/Fixtures/functional')->name('*.test')->files() as $file) { foreach (Finder::create()->in(__DIR__.'/Fixtures/functional')->name('*.test')->files() as $file) {
$tests[] = array($file->getRealPath()); $tests[basename($file)] = array($file->getRealPath());
} }
return $tests; return $tests;
@ -144,23 +191,6 @@ class AllFunctionalTest extends TestCase
$data = array(); $data = array();
$section = null; $section = null;
$testDir = self::getUniqueTmpDirectory();
$this->testDir = $testDir;
$varRegex = '#%([a-zA-Z_-]+)%#';
$variableReplacer = function ($match) use (&$data, $testDir) {
list(, $var) = $match;
switch ($var) {
case 'testDir':
$data['test_dir'] = $testDir;
return $testDir;
default:
throw new \InvalidArgumentException(sprintf('Unknown variable "%s". Supported variables: "testDir"', $var));
}
};
foreach ($tokens as $token) { foreach ($tokens as $token) {
if ('' === $token && null === $section) { if ('' === $token && null === $section) {
continue; continue;
@ -176,24 +206,20 @@ class AllFunctionalTest extends TestCase
// Allow sections to validate, or modify their section data. // Allow sections to validate, or modify their section data.
switch ($section) { switch ($section) {
case 'RUN':
$sectionData = preg_replace_callback($varRegex, $variableReplacer, $sectionData);
break;
case 'EXPECT-EXIT-CODE': case 'EXPECT-EXIT-CODE':
$sectionData = (int) $sectionData; $sectionData = (int) $sectionData;
break; break;
case 'RUN':
case 'EXPECT': case 'EXPECT':
case 'EXPECT-REGEX': case 'EXPECT-REGEX':
case 'EXPECT-ERROR': case 'EXPECT-REGEXES':
case 'EXPECT-ERROR-REGEX': $sectionData = trim($sectionData);
$sectionData = preg_replace_callback($varRegex, $variableReplacer, $sectionData);
break; break;
default: default:
throw new \RuntimeException(sprintf( throw new \RuntimeException(sprintf(
'Unknown section "%s". Allowed sections: "RUN", "EXPECT", "EXPECT-ERROR", "EXPECT-EXIT-CODE", "EXPECT-REGEX", "EXPECT-ERROR-REGEX". ' 'Unknown section "%s". Allowed sections: "RUN", "EXPECT", "EXPECT-EXIT-CODE", "EXPECT-REGEX", "EXPECT-REGEXES". '
.'Section headers must be written as "--HEADER_NAME--".', .'Section headers must be written as "--HEADER_NAME--".',
$section $section
)); ));
@ -207,8 +233,8 @@ class AllFunctionalTest extends TestCase
if (!isset($data['RUN'])) { if (!isset($data['RUN'])) {
throw new \RuntimeException('The test file must have a section named "RUN".'); throw new \RuntimeException('The test file must have a section named "RUN".');
} }
if (!isset($data['EXPECT']) && !isset($data['EXPECT-ERROR']) && !isset($data['EXPECT-REGEX']) && !isset($data['EXPECT-ERROR-REGEX'])) { if (!isset($data['EXPECT']) && !isset($data['EXPECT-REGEX']) && !isset($data['EXPECT-REGEXES'])) {
throw new \RuntimeException('The test file must have a section named "EXPECT", "EXPECT-ERROR", "EXPECT-REGEX", or "EXPECT-ERROR-REGEX".'); throw new \RuntimeException('The test file must have a section named "EXPECT", "EXPECT-REGEX", or "EXPECT-REGEXES".');
} }
return $data; return $data;

View File

@ -61,7 +61,7 @@ class ApplicationTest extends TestCase
$outputMock->expects($this->at($index++)) $outputMock->expects($this->at($index++))
->method("write") ->method("write")
->with($this->equalTo('<warning>You are running composer with Xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug</warning>')); ->with($this->equalTo('<warning>Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug</warning>'));
} }
$outputMock->expects($this->at($index++)) $outputMock->expects($this->at($index++))
@ -70,7 +70,7 @@ class ApplicationTest extends TestCase
$outputMock->expects($this->at($index++)) $outputMock->expects($this->at($index++))
->method("write") ->method("write")
->with($this->equalTo(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']))); ->with($this->equalTo(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 (!defined('COMPOSER_DEV_WARNING_TIME')) { if (!defined('COMPOSER_DEV_WARNING_TIME')) {
define('COMPOSER_DEV_WARNING_TIME', time() - 1); define('COMPOSER_DEV_WARNING_TIME', time() - 1);

View File

@ -1447,9 +1447,9 @@ EOF;
$package->setAutoload(array( $package->setAutoload(array(
'psr-0' => array('Foo' => '../path/../src'), 'psr-0' => array('Foo' => '../path/../src'),
'psr-4' => array('Acme\Foo\\' => '../path/../src-psr4'), 'psr-4' => array('Acme\Foo\\' => '../path/../src-psr4'),
'classmap' => array('../classmap'), 'classmap' => array('../classmap', '../classmap2/subdir', 'classmap3', 'classmap4'),
'files' => array('../test.php'), 'files' => array('../test.php'),
'exclude-from-classmap' => array('./../classmap/excluded'), 'exclude-from-classmap' => array('./../classmap/excluded', '../classmap2', 'classmap3/classes.php', 'classmap4/*/classes.php'),
)); ));
$this->repository->expects($this->once()) $this->repository->expects($this->once())
@ -1458,9 +1458,15 @@ EOF;
$this->fs->ensureDirectoryExists($this->workingDir.'/src/Foo'); $this->fs->ensureDirectoryExists($this->workingDir.'/src/Foo');
$this->fs->ensureDirectoryExists($this->workingDir.'/classmap/excluded'); $this->fs->ensureDirectoryExists($this->workingDir.'/classmap/excluded');
$this->fs->ensureDirectoryExists($this->workingDir.'/classmap2/subdir');
$this->fs->ensureDirectoryExists($this->workingDir.'/working-dir/classmap3');
$this->fs->ensureDirectoryExists($this->workingDir.'/working-dir/classmap4/foo/');
file_put_contents($this->workingDir.'/src/Foo/Bar.php', '<?php namespace Foo; class Bar {}'); file_put_contents($this->workingDir.'/src/Foo/Bar.php', '<?php namespace Foo; class Bar {}');
file_put_contents($this->workingDir.'/classmap/classes.php', '<?php namespace Foo; class Foo {}'); file_put_contents($this->workingDir.'/classmap/classes.php', '<?php namespace Foo; class Foo {}');
file_put_contents($this->workingDir.'/classmap/excluded/classes.php', '<?php namespace Foo; class Boo {}'); file_put_contents($this->workingDir.'/classmap/excluded/classes.php', '<?php namespace Foo; class Boo {}');
file_put_contents($this->workingDir.'/classmap2/subdir/classes.php', '<?php namespace Foo; class Boo2 {}');
file_put_contents($this->workingDir.'/working-dir/classmap3/classes.php', '<?php namespace Foo; class Boo3 {}');
file_put_contents($this->workingDir.'/working-dir/classmap4/foo/classes.php', '<?php namespace Foo; class Boo4 {}');
file_put_contents($this->workingDir.'/test.php', '<?php class Foo {}'); file_put_contents($this->workingDir.'/test.php', '<?php class Foo {}');
$this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_14'); $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_14');

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitFilesAutoloadOrder
} }
spl_autoload_register(array('ComposerAutoloaderInitFilesAutoloadOrder', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitFilesAutoloadOrder', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoloadOrder', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoloadOrder', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitFilesAutoload
} }
spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitFilesAutoload
} }
spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php'; $includePaths = require __DIR__ . '/include_paths.php';

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitFilesAutoload
} }
spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitFilesAutoload', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitIncludePath
} }
spl_autoload_register(array('ComposerAutoloaderInitIncludePath', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitIncludePath', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitIncludePath', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitIncludePath', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@ -23,7 +23,7 @@ class ComposerAutoloaderInitTargetDir
} }
spl_autoload_register(array('ComposerAutoloaderInitTargetDir', 'loadClassLoader'), true, true); spl_autoload_register(array('ComposerAutoloaderInitTargetDir', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitTargetDir', 'loadClassLoader')); spl_autoload_unregister(array('ComposerAutoloaderInitTargetDir', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());

View File

@ -35,7 +35,7 @@ class ConfigTest extends TestCase
$data = array(); $data = array();
$data['local config inherits system defaults'] = array( $data['local config inherits system defaults'] = array(
array( array(
'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), 'packagist.org' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
), ),
array(), array(),
); );
@ -58,7 +58,7 @@ class ConfigTest extends TestCase
array( array(
1 => array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), 1 => array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'),
0 => array('type' => 'pear', 'url' => 'http://pear.composer.org'), 0 => array('type' => 'pear', 'url' => 'http://pear.composer.org'),
'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), 'packagist.org' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
), ),
array( array(
array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'),
@ -69,7 +69,7 @@ class ConfigTest extends TestCase
$data['system config adds above core defaults'] = array( $data['system config adds above core defaults'] = array(
array( array(
'example.com' => array('type' => 'composer', 'url' => 'http://example.com'), 'example.com' => array('type' => 'composer', 'url' => 'http://example.com'),
'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), 'packagist.org' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
), ),
array(), array(),
array( array(
@ -104,9 +104,27 @@ class ConfigTest extends TestCase
), ),
); );
$data['local config redefining packagist.org by URL override it if no named keys are used'] = array(
array(
array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
),
array(
array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
),
);
$data['local config redefining packagist.org by URL override it also with named keys'] = array(
array(
'example' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
),
array(
'example' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
),
);
$data['incorrect local config does not cause ErrorException'] = array( $data['incorrect local config does not cause ErrorException'] = array(
array( array(
'packagist.org' => array('type' => 'composer', 'url' => 'https?://repo.packagist.org', 'allow_ssl_downgrade' => true), 'packagist.org' => array('type' => 'composer', 'url' => 'https://repo.packagist.org'),
'type' => 'vcs', 'type' => 'vcs',
'url' => 'http://example.com', 'url' => 'http://example.com',
), ),

View File

@ -122,12 +122,7 @@ class GitDownloaderTest extends TestCase
$processExecutor->expects($this->at(2)) $processExecutor->expects($this->at(2))
->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' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0));
$processExecutor->expects($this->at(3))
->method('execute')
->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));
$downloader = $this->getDownloaderMock(null, null, $processExecutor); $downloader = $this->getDownloaderMock(null, null, $processExecutor);
@ -198,12 +193,7 @@ class GitDownloaderTest extends TestCase
$processExecutor->expects($this->at(5)) $processExecutor->expects($this->at(5))
->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' -- || git checkout -B 'master' 'composer/master' --) && git reset --hard '1234567890123456789012345678901234567890' --")), $this->equalTo(null), $this->equalTo($this->winCompat('composerPath')))
->will($this->returnValue(0));
$processExecutor->expects($this->at(6))
->method('execute')
->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));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor); $downloader = $this->getDownloaderMock(null, $config, $processExecutor);

View File

@ -1,4 +1,4 @@
--RUN-- --RUN--
create-project seld/jsonlint %testDir% 1.0.0 --prefer-source -n create-project seld/jsonlint foo 1.0.0 --prefer-source -n
--EXPECT-ERROR-REGEX-- --EXPECT-REGEX--
{Installing seld/jsonlint \(1.0.0\): Cloning [a-f0-9]{10}( from cache)?} {- Installing seld/jsonlint \(1.0.0\): Cloning [a-f0-9]{10}( from cache)?}

View File

@ -1,4 +1,4 @@
--RUN-- --RUN--
create-project --repository=packages.json -v seld/jsonlint %testDir% dev-master create-project --repository=packages.json -v seld/jsonlint foo dev-main
--EXPECT-ERROR-REGEX-- --EXPECT-REGEX--
{^Installing seld/jsonlint \(dev-master [a-f0-9]{40}\)}m {^Installing seld/jsonlint \(dev-main [a-f0-9]{40}\)}m

View File

@ -9,8 +9,9 @@
"validator" "validator"
], ],
"homepage": "", "homepage": "",
"version": "dev-master", "version": "dev-main",
"version_normalized": "9999999-dev", "version_normalized": "dev-main",
"default-branch": true,
"license": [ "license": [
"MIT" "MIT"
], ],
@ -41,4 +42,4 @@
"php": ">=5.3.0" "php": ">=5.3.0"
} }
} }
] ]

View File

@ -0,0 +1,50 @@
--TEST--
Verify that a conflict with all dependencies option enabled don't recommend to use the option
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "locked/pkg", "version": "dev-master", "require": {"locked/dependency": "1.0.0"}, "default-branch": true }
]
}
],
"require": {
"locked/pkg": "*@dev"
}
}
--LOCK--
{
"packages": [
{ "name": "locked/pkg", "version": "dev-master", "require": {"locked/dependency": "1.0.0"}, "default-branch": true },
{ "name": "locked/dependency", "version": "1.0.0" }
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
--RUN--
update locked/dependency --with-all-dependencies
--EXPECT-EXIT-CODE--
2
--EXPECT-OUTPUT--
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- locked/pkg dev-master requires locked/dependency 1.0.0 -> found locked/dependency[1.0.0] in lock file but not in remote repositories, make sure you avoid updating this package to keep the one from lock file.
- locked/pkg is locked to version dev-master and an update of this package was not requested.
--EXPECT--

View File

@ -17,6 +17,20 @@ use Composer\Semver\VersionParser;
class InstalledVersionsTest extends TestCase class InstalledVersionsTest extends TestCase
{ {
public static function setUpBeforeClass()
{
// disable multiple-ClassLoader-based checks of InstalledVersions by making it seem like no
// class loaders are registered
$prop = new \ReflectionProperty('Composer\Autoload\ClassLoader', 'registeredLoaders');
$prop->setAccessible(true);
$prop->setValue(array());
}
public static function tearDownAfterClass()
{
self::setUpBeforeClass();
}
public function setUp() public function setUp()
{ {
InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php'); InstalledVersions::reload(require __DIR__.'/Repository/Fixtures/installed.php');

File diff suppressed because it is too large Load Diff

View File

@ -18,20 +18,44 @@ use Composer\Package\Archiver\ZipArchiver;
class ZipArchiverTest extends ArchiverTest class ZipArchiverTest extends ArchiverTest
{ {
public function testZipArchive() /**
* @param string $include
*
* @dataProvider provideGitignoreExcludeNegationTestCases
*/
public function testGitignoreExcludeNegation($include)
{
$this->testZipArchive(array(
'docs/README.md' => '# The doc',
'.gitignore' => "/*\n.*\n!.git*\n$include",
));
}
public function provideGitignoreExcludeNegationTestCases()
{
return array(
array('!/docs'),
array('!/docs/'),
);
}
public function testZipArchive(array $files = array())
{ {
if (!class_exists('ZipArchive')) { if (!class_exists('ZipArchive')) {
$this->markTestSkipped('Cannot run ZipArchiverTest, missing class "ZipArchive".'); $this->markTestSkipped('Cannot run ZipArchiverTest, missing class "ZipArchive".');
} }
$files = array( if (empty($files)) {
'file.txt', $files = array(
'foo/bar/baz', 'file.txt' => NULL,
'x/baz', 'foo/bar/baz' => NULL,
'x/includeme', 'x/baz' => NULL,
); 'x/includeme' => NULL,
if (!Platform::isWindows()) { );
$files[] = 'foo' . getcwd() . '/file.txt';
if (!Platform::isWindows()) {
$files['foo' . getcwd() . '/file.txt'] = NULL;
}
} }
// Set up repository // Set up repository
$this->setupDummyRepo($files); $this->setupDummyRepo($files);
@ -41,12 +65,12 @@ class ZipArchiverTest extends ArchiverTest
// Test archive // Test archive
$archiver = new ZipArchiver(); $archiver = new ZipArchiver();
$archiver->archive($package->getSourceUrl(), $target, 'zip'); $archiver->archive($package->getSourceUrl(), $target, 'zip');
$this->assertFileExists($target); static::assertFileExists($target);
$zip = new ZipArchive(); $zip = new ZipArchive();
$res = $zip->open($target); $res = $zip->open($target);
self::assertTrue($res, 'Failed asserting that Zip file can be opened'); static::assertTrue($res, 'Failed asserting that Zip file can be opened');
foreach ($files as $file) { foreach ($files as $path => $content) {
$this->assertSame('content', $zip->getFromName($file), 'Failed asserting that Zip contains ' . $file); static::assertSame($content, $zip->getFromName($path), 'Failed asserting that Zip contains ' . $path);
} }
$zip->close(); $zip->close();
@ -57,12 +81,15 @@ class ZipArchiverTest extends ArchiverTest
* Create a local dummy repository to run tests against! * Create a local dummy repository to run tests against!
* @param array $files * @param array $files
*/ */
protected function setupDummyRepo($files) protected function setupDummyRepo(array &$files)
{ {
$currentWorkDir = getcwd(); $currentWorkDir = getcwd();
chdir($this->testDir); chdir($this->testDir);
foreach ($files as $file) { foreach ($files as $path => $content) {
$this->writeFile($file, 'content', $currentWorkDir); if ($files[$path] === NULL) {
$files[$path] = 'content';
}
$this->writeFile($path, $files[$path], $currentWorkDir);
} }
chdir($currentWorkDir); chdir($currentWorkDir);

View File

@ -202,28 +202,4 @@ class RootPackageLoaderTest extends TestCase
$this->assertEquals("dev-latest-production", $package->getPrettyVersion()); $this->assertEquals("dev-latest-production", $package->getPrettyVersion());
} }
/**
* @dataProvider provideExtraBranchVersion
*/
public function testLoadExtraBranchVersion($branchVersion)
{
$package = $this->loadPackage(array(
'extra' => array(
'branch-version' => $branchVersion,
),
));
$this->assertEquals('1.2.x-dev', $package->getPrettyVersion());
}
public function provideExtraBranchVersion()
{
return array(
array('1.2'),
array('1.2.x'),
array('1.2-dev'),
array('1.2.x-dev'),
);
}
} }

View File

@ -18,6 +18,7 @@ use Composer\Installer\PluginInstaller;
use Composer\Package\CompletePackage; use Composer\Package\CompletePackage;
use Composer\Package\Loader\JsonLoader; use Composer\Package\Loader\JsonLoader;
use Composer\Package\Loader\ArrayLoader; use Composer\Package\Loader\ArrayLoader;
use Composer\Package\RootPackage;
use Composer\Plugin\PluginManager; use Composer\Plugin\PluginManager;
use Composer\IO\BufferIO; use Composer\IO\BufferIO;
use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventDispatcher;
@ -111,6 +112,7 @@ class PluginInstallerTest extends TestCase
$this->composer->setInstallationManager($im); $this->composer->setInstallationManager($im);
$this->composer->setAutoloadGenerator($this->autoloadGenerator); $this->composer->setAutoloadGenerator($this->autoloadGenerator);
$this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io)); $this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io));
$this->composer->setPackage(new RootPackage('dummy/root', '1.0.0.0', '1.0.0'));
$this->pm = new PluginManager($this->io, $this->composer); $this->pm = new PluginManager($this->io, $this->composer);
$this->composer->setPluginManager($this->pm); $this->composer->setPluginManager($this->pm);

View File

@ -55,7 +55,7 @@ class ComposerRepositoryTest extends TestCase
$repository $repository
->expects($this->at(2)) ->expects($this->at(2))
->method('createPackages') ->method('createPackages')
->with($this->identicalTo($expected), $this->equalTo('Composer\Package\CompletePackage')) ->with($this->identicalTo($expected), $this->equalTo('root file (http://example.org/packages.json)'))
->will($this->returnValue($stubs)); ->will($this->returnValue($stubs));
// Triggers initialization // Triggers initialization

View File

@ -1,6 +0,0 @@
{
"name": "test/path-branch-versioned",
"extra": {
"branch-version": "1.2"
}
}

View File

@ -67,23 +67,6 @@ class PathRepositoryTest extends TestCase
$this->assertNotEmpty($packageVersion); $this->assertNotEmpty($packageVersion);
} }
public function testLoadPackageFromFileSystemWithExtraBranchVersion()
{
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
->getMock();
$config = new \Composer\Config();
$versionGuesser = null;
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', 'with-branch-version'));
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
$packages = $repository->getPackages();
$this->assertEquals(1, $repository->count());
$this->assertTrue($repository->hasPackage($this->getPackage('test/path-branch-versioned', '1.2.x-dev')));
}
public function testLoadPackageFromFileSystemWithWildcard() public function testLoadPackageFromFileSystemWithWildcard()
{ {
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface') $ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
@ -95,16 +78,50 @@ class PathRepositoryTest extends TestCase
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*')); $repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
$repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config); $repository = new PathRepository(array('url' => $repositoryUrl), $ioInterface, $config);
$packages = $repository->getPackages(); $packages = $repository->getPackages();
$result = array(); $names = array();
$this->assertGreaterThanOrEqual(3, $repository->count()); $this->assertEquals(2, $repository->count());
foreach ($packages as $package) { $package = $packages[0];
$result[$package->getName()] = $package->getPrettyVersion(); $names[] = $package->getName();
}
ksort($result); $package = $packages[1];
$this->assertSame(array('test/path-branch-versioned' => '1.2.x-dev', 'test/path-unversioned' => $result['test/path-unversioned'], 'test/path-versioned' => '0.0.2'), $result); $names[] = $package->getName();
sort($names);
$this->assertEquals(array('test/path-unversioned', 'test/path-versioned'), $names);
}
public function testLoadPackageWithExplicitVersions()
{
$ioInterface = $this->getMockBuilder('Composer\IO\IOInterface')
->getMock();
$config = new \Composer\Config();
$versionGuesser = null;
$options = array(
'versions' => array(
'test/path-unversioned' => '4.3.2.1',
'test/path-versioned' => '3.2.1.0',
),
);
$repositoryUrl = implode(DIRECTORY_SEPARATOR, array(__DIR__, 'Fixtures', 'path', '*'));
$repository = new PathRepository(array('url' => $repositoryUrl, 'options' => $options), $ioInterface, $config);
$packages = $repository->getPackages();
$versions = array();
$this->assertEquals(2, $repository->count());
$package = $packages[0];
$versions[$package->getName()] = $package->getVersion();
$package = $packages[1];
$versions[$package->getName()] = $package->getVersion();
ksort($versions);
$this->assertSame(array('test/path-unversioned' => '4.3.2.1', 'test/path-versioned' => '3.2.1.0'), $versions);
} }
/** /**