Browse Source

Merge branch 'master' into 2.0

Jordi Boggiano 5 years ago
parent
commit
d63eb8179e

+ 9 - 0
CHANGELOG.md

@@ -1,3 +1,11 @@
+### [1.10.0] 2020-03-10
+
+  * Added `bearer` auth config to authenticate using `Authorization: Bearer <token>` headers
+  * Added `plugin-api-version` in composer.lock so future Composer versions know if they are running a lock file which was not built by the correct version
+  * Fixed composer fund command and funding info parsing to be more useful
+  * Fixed issue where --no-dev autoload generation was excluding some packages which should not have been excluded
+  * Fixed 1.10-RC regression in create project's handling of absolute paths
+
 ### [1.10.0-RC] 2020-02-14
 
   * Breaking: `composer global exec ...` now executes the process in the current working directory instead of executing it in the global directory.
@@ -811,6 +819,7 @@
 
   * Initial release
 
+[1.10.0]: https://github.com/composer/composer/compare/1.10.0-RC...1.10.0
 [1.10.0-RC]: https://github.com/composer/composer/compare/1.9.3...1.10.0-RC
 [1.9.3]: https://github.com/composer/composer/compare/1.9.2...1.9.3
 [1.9.2]: https://github.com/composer/composer/compare/1.9.1...1.9.2

+ 42 - 28
composer.lock

@@ -185,16 +185,16 @@
         },
         {
             "name": "composer/xdebug-handler",
-            "version": "1.4.0",
+            "version": "1.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/xdebug-handler.git",
-                "reference": "cbe23383749496fe0f373345208b79568e4bc248"
+                "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248",
-                "reference": "cbe23383749496fe0f373345208b79568e4bc248",
+                "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7",
+                "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7",
                 "shasum": ""
             },
             "require": {
@@ -225,12 +225,13 @@
                 "Xdebug",
                 "performance"
             ],
-            "support": {
-                "irc": "irc://irc.freenode.org/composer",
-                "issues": "https://github.com/composer/xdebug-handler/issues",
-                "source": "https://github.com/composer/xdebug-handler/tree/1.4.0"
-            },
-            "time": "2019-11-06T16:40:04+00:00"
+            "funding": [
+                {
+                    "url": "https://packagist.com",
+                    "type": "custom"
+                }
+            ],
+            "time": "2020-03-01T12:26:26+00:00"
         },
         {
             "name": "justinrainbow/json-schema",
@@ -935,6 +936,10 @@
                 "constructor",
                 "instantiate"
             ],
+            "support": {
+                "issues": "https://github.com/doctrine/instantiator/issues",
+                "source": "https://github.com/doctrine/instantiator/tree/master"
+            },
             "time": "2015-06-14T21:17:01+00:00"
         },
         {
@@ -978,26 +983,20 @@
             "license": [
                 "MIT"
             ],
-            "authors": [
-                {
-                    "name": "Mike van Riel",
-                    "email": "mike.vanriel@naenius.com"
-                }
-            ],
             "time": "2016-01-25T08:17:30+00:00"
         },
         {
             "name": "phpspec/prophecy",
-            "version": "v1.10.2",
+            "version": "v1.10.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpspec/prophecy.git",
-                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9"
+                "reference": "451c3cd1418cf640de218914901e51b064abb093"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
-                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
+                "reference": "451c3cd1418cf640de218914901e51b064abb093",
                 "shasum": ""
             },
             "require": {
@@ -1047,7 +1046,7 @@
                 "spy",
                 "stub"
             ],
-            "time": "2020-01-20T15:57:02+00:00"
+            "time": "2020-03-05T15:02:03+00:00"
         },
         {
             "name": "sebastian/comparator",
@@ -1287,23 +1286,23 @@
         },
         {
             "name": "symfony/phpunit-bridge",
-            "version": "v3.4.37",
+            "version": "v3.4.38",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/phpunit-bridge.git",
-                "reference": "ebfd1b428ffc14306e843092763f228bfba168d0"
+                "reference": "c02893ae43532b46a4f0e0f207d088b939f278d9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/ebfd1b428ffc14306e843092763f228bfba168d0",
-                "reference": "ebfd1b428ffc14306e843092763f228bfba168d0",
+                "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/c02893ae43532b46a4f0e0f207d088b939f278d9",
+                "reference": "c02893ae43532b46a4f0e0f207d088b939f278d9",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3"
             },
             "conflict": {
-                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0|<6.4,>=6.0"
             },
             "suggest": {
                 "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
@@ -1348,7 +1347,21 @@
             ],
             "description": "Symfony PHPUnit Bridge",
             "homepage": "https://symfony.com",
-            "time": "2020-01-14T14:27:59+00:00"
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-02-21T08:01:47+00:00"
         }
     ],
     "aliases": [],
@@ -1362,5 +1375,6 @@
     "platform-dev": [],
     "platform-overrides": {
         "php": "5.3.9"
-    }
+    },
+    "plugin-api-version": "1.1.0"
 }

+ 12 - 6
doc/06-config.md

@@ -79,16 +79,16 @@ an OAuth token for GitHub.
 
 A list of domain names and oauth keys. For example using `{"gitlab.com":
 "oauthtoken"}` as the value of this option will use `oauthtoken` to access
-private repositories on gitlab. Please note: If the package is not hosted at 
-gitlab.com the domain names must be also specified with the 
+private repositories on gitlab. Please note: If the package is not hosted at
+gitlab.com the domain names must be also specified with the
 [`gitlab-domains`](06-config.md#gitlab-domains) option.
 
 ## gitlab-token
 
 A list of domain names and private tokens. For example using `{"gitlab.com":
 "privatetoken"}` as the value of this option will use `privatetoken` to access
-private repositories on gitlab. Please note: If the package is not hosted at 
-gitlab.com the domain names must be also specified with the 
+private repositories on gitlab. Please note: If the package is not hosted at
+gitlab.com the domain names must be also specified with the
 [`gitlab-domains`](06-config.md#gitlab-domains) option.
 
 ## disable-tls
@@ -129,11 +129,17 @@ A list of domain names and username/passwords to authenticate against them. For
 example using `{"example.org": {"username": "alice", "password": "foo"}}` as the
 value of this option will let Composer authenticate against example.org.
 
-> **Note:** Authentication-related config options like `http-basic` and
+> **Note:** Authentication-related config options like `http-basic`, `bearer` and
 > `github-oauth` can also be specified inside a `auth.json` file that goes
 > besides your `composer.json`. That way you can gitignore it and every
 > developer can place their own credentials in there.
 
+## bearer
+
+A list of domain names and tokens to authenticate against them. For example using
+`{"example.org": "foo"}` as the value of this option will let Composer authenticate
+against example.org using an `Authorization: Bearer foo` header.
+
 ## platform
 
 Lets you fake platform packages (PHP and extensions) so that you can emulate a
@@ -298,7 +304,7 @@ in the composer home, cache, and data directories.
 
 ## lock
 
-Defaults to `true`. If set to `false`, Composer will not create a `composer.lock` 
+Defaults to `true`. If set to `false`, Composer will not create a `composer.lock`
 file.
 
 &larr; [Repositories](05-repositories.md)  |  [Community](07-community.md) &rarr;

+ 1 - 1
doc/articles/versions.md

@@ -54,7 +54,7 @@ v2.0.2
 
 Normally, Composer deals with tags (as opposed to branches -- if you don't
 know what this means, read up on
-[version control systems](https://en.wikipedia.org/wiki/Version_control#Common_vocabulary)).
+[version control systems](https://en.wikipedia.org/wiki/Version_control#Common_terminology)).
 When you write a version constraint, it may reference a specific tag (e.g.,
 `1.1`) or it may reference a valid range of tags (e.g., `>=1.1 <2.0`, or
 `~4.0`). To resolve these constraints, Composer first asks the VCS to list

+ 10 - 1
res/composer-schema.json

@@ -140,7 +140,16 @@
                 "gitlab-token": {
                     "type": "object",
                     "description": "A hash of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.",
-                    "additionalProperties": true
+                    "additionalProperties": {
+                        "type": "string"
+                    }
+                },
+                "bearer": {
+                    "type": "object",
+                    "description": "A hash of domain name => bearer authentication token, for example {\"example.com\":\"<token>\"}.",
+                    "additionalProperties": {
+                        "type": "string"
+                    }
                 },
                 "disable-tls": {
                     "type": "boolean",

+ 24 - 10
src/Composer/Autoload/AutoloadGenerator.php

@@ -236,6 +236,7 @@ EOF;
 
         // flatten array
         $classMap = array();
+        $ambiguousClasses = array();
         if ($scanPsr0Packages) {
             $namespacesToScan = array();
 
@@ -256,14 +257,23 @@ EOF;
                             continue;
                         }
 
-                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap);
+                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespace, $group['type'], $classMap, $ambiguousClasses);
                     }
                 }
             }
         }
 
         foreach ($autoloads['classmap'] as $dir) {
-            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap);
+            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, null, $classMap, $ambiguousClasses);
+        }
+
+        foreach ($ambiguousClasses as $className => $ambigiousPaths) {
+            $cleanPath = str_replace(array('$vendorDir . \'', '$baseDir . \'', "',\n"), array($vendorPath, $basePath, ''), $classMap[$className]);
+
+            $this->io->writeError(
+                '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
+                ' was found '. (count($ambigiousPaths) + 1) .'x: in "'.$cleanPath.'" and "'. implode('", "', $ambigiousPaths) .'", the first will be used.</warning>'
+            );
         }
 
         ksort($classMap);
@@ -326,17 +336,14 @@ EOF;
         return 0;
     }
 
-    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap = array())
+    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, $autoloadType = null, array $classMap, array &$ambiguousClasses)
     {
         foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter, $autoloadType) as $class => $path) {
             $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
             if (!isset($classMap[$class])) {
                 $classMap[$class] = $pathCode;
             } elseif ($this->io && $classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) {
-                $this->io->writeError(
-                    '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
-                    ' was found in both "'.str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]).'" and "'.$path.'", the first will be used.</warning>'
-                );
+                $ambiguousClasses[$class][] = $path;
             }
         }
 
@@ -393,8 +400,8 @@ EOF;
     /**
      * Compiles an ordered list of namespace => path mappings
      *
-     * @param  array            $packageMap  array of array(package, installDir-relative-to-composer.json)
-     * @param  PackageInterface $mainPackage root package instance
+     * @param  array            $packageMap                  array of array(package, installDir-relative-to-composer.json)
+     * @param  PackageInterface $mainPackage                 root package instance
      * @param  bool             $filterOutRequireDevPackages whether to filter out require-dev packages
      * @return array            array('psr-0' => array('Ns\\Foo' => array('installDir')))
      */
@@ -939,16 +946,23 @@ INITIALIZER;
     {
         $packages = array();
         $include = array();
+        $replacedBy = array();
 
         foreach ($packageMap as $item) {
             $package = $item[0];
             $name = $package->getName();
             $packages[$name] = $package;
+            foreach ($package->getReplaces() as $replace) {
+                $replacedBy[$replace->getTarget()] = $name;
+            }
         }
 
-        $add = function (PackageInterface $package) use (&$add, $packages, &$include) {
+        $add = function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy) {
             foreach ($package->getRequires() as $link) {
                 $target = $link->getTarget();
+                if (isset($replacedBy[$target])) {
+                    $target = $replacedBy[$target];
+                }
                 if (!isset($include[$target])) {
                     $include[$target] = true;
                     if (isset($packages[$target])) {

+ 3 - 3
src/Composer/Command/ConfigCommand.php

@@ -187,7 +187,7 @@ EOT
         }
         if ($input->getOption('global') && !$this->authConfigFile->exists()) {
             touch($this->authConfigFile->getPath());
-            $this->authConfigFile->write(array('bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject));
+            $this->authConfigFile->write(array('bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject));
             Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
         }
 
@@ -667,7 +667,7 @@ EOT
         }
 
         // handle auth
-        if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic)\.(.+)/', $settingKey, $matches)) {
+        if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|bearer)\.(.+)/', $settingKey, $matches)) {
             if ($input->getOption('unset')) {
                 $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
@@ -681,7 +681,7 @@ EOT
                 }
                 $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                 $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('consumer-key' => $values[0], 'consumer-secret' => $values[1]));
-            } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token'), true)) {
+            } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'), true)) {
                 if (1 !== count($values)) {
                     throw new \RuntimeException('Too many arguments, expected only one token');
                 }

+ 7 - 4
src/Composer/Command/CreateProjectCommand.php

@@ -303,13 +303,16 @@ EOT
         // if no directory was specified, use the 2nd part of the package name
         if (null === $directory) {
             $parts = explode("/", $name, 2);
-            $directory = array_pop($parts);
+            $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);
         }
 
-        $directory = getcwd() . DIRECTORY_SEPARATOR . $directory;
-        $io->writeError('<info>Creating a "' . $packageName . '" project at "' . $directory . '"</info>');
-
         $fs = new Filesystem();
+        if (!$fs->isAbsolutePath($directory)) {
+            $directory = getcwd() . DIRECTORY_SEPARATOR . $directory;
+        }
+
+        $io->writeError('<info>Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(getcwd(), $directory, true) . '"</info>');
+
         if (file_exists($directory)) {
             if (!is_dir($directory)) {
                 throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.');

+ 23 - 9
src/Composer/Command/FundCommand.php

@@ -14,6 +14,7 @@ namespace Composer\Command;
 
 use Composer\Package\CompletePackageInterface;
 use Composer\Package\AliasPackage;
+use Composer\Repository\CompositeRepository;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -36,20 +37,19 @@ class FundCommand extends BaseCommand
         $composer = $this->getComposer();
 
         $repo = $composer->getRepositoryManager()->getLocalRepository();
+        $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
         $fundings = array();
         foreach ($repo->getPackages() as $package) {
             if ($package instanceof AliasPackage) {
                 continue;
             }
-            if ($package instanceof CompletePackageInterface && $funding = $package->getFunding()) {
-                foreach ($funding as $fundingOption) {
-                    list($vendor, $packageName) = explode('/', $package->getPrettyName());
-                    $url = $fundingOption['url'];
-                    if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && preg_match('{^https://github.com/([^/]+)$}', $url, $match)) {
-                        $url = 'https://github.com/sponsors/'.$match[1];
-                    }
-                    $fundings[$vendor][$url][] = $packageName;
-                }
+            $latest = $remoteRepos->findPackage($package->getName(), 'dev-master');
+            if ($latest instanceof CompletePackageInterface && $latest->getFunding()) {
+                $fundings = $this->insertFundingData($fundings, $latest);
+                continue;
+            }
+            if ($package instanceof CompletePackageInterface && $package->getFunding()) {
+                $fundings = $this->insertFundingData($fundings, $package);
             }
         }
 
@@ -86,4 +86,18 @@ class FundCommand extends BaseCommand
 
         return 0;
     }
+
+    private function insertFundingData(array $fundings, CompletePackageInterface $package)
+    {
+        foreach ($package->getFunding() as $fundingOption) {
+            list($vendor, $packageName) = explode('/', $package->getPrettyName());
+            $url = $fundingOption['url'];
+            if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && preg_match('{^https://github.com/([^/]+)$}', $url, $match)) {
+                $url = 'https://github.com/sponsors/'.$match[1];
+            }
+            $fundings[$vendor][$url][] = $packageName;
+        }
+
+        return $fundings;
+    }
 }

+ 1 - 1
src/Composer/Command/ShowCommand.php

@@ -495,7 +495,7 @@ EOT
                     }
                     $io->write('');
                     if (isset($package['warning'])) {
-                        $io->writeError('<warning>' . $package['warning'] . '</warning>');
+                        $io->write('<warning>' . $package['warning'] . '</warning>');
                     }
                 }
 

+ 2 - 1
src/Composer/Config.php

@@ -70,6 +70,7 @@ class Config
         // gitlab-oauth
         // gitlab-token
         // http-basic
+        // bearer
     );
 
     public static $defaultRepositories = array(
@@ -133,7 +134,7 @@ class Config
         // override defaults with given config
         if (!empty($config['config']) && is_array($config['config'])) {
             foreach ($config['config'] as $key => $val) {
-                if (in_array($key, array('bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic')) && isset($this->config[$key])) {
+                if (in_array($key, array('bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer')) && isset($this->config[$key])) {
                     $this->config[$key] = array_merge($this->config[$key], $val);
                 } elseif ('preferred-install' === $key && isset($this->config[$key])) {
                     if (is_array($val) || is_array($this->config[$key])) {

+ 2 - 2
src/Composer/Config/JsonConfigSource.php

@@ -96,7 +96,7 @@ class JsonConfigSource implements ConfigSourceInterface
     {
         $authConfig = $this->authConfig;
         $this->manipulateJson('addConfigSetting', $name, $value, function (&$config, $key, $val) use ($authConfig) {
-            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) {
+            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                 list($key, $host) = explode('.', $key, 2);
                 if ($authConfig) {
                     $config[$key][$host] = $val;
@@ -116,7 +116,7 @@ class JsonConfigSource implements ConfigSourceInterface
     {
         $authConfig = $this->authConfig;
         $this->manipulateJson('removeConfigSetting', $name, function (&$config, $key) use ($authConfig) {
-            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) {
+            if (preg_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                 list($key, $host) = explode('.', $key, 2);
                 if ($authConfig) {
                     unset($config[$key][$host]);

+ 2 - 1
src/Composer/Console/Application.php

@@ -131,7 +131,8 @@ class Application extends BaseApplication
 
         if ($input->hasParameterOption('--no-cache')) {
             $io->writeError('Disabling cache usage', true, IOInterface::DEBUG);
-            putenv('COMPOSER_CACHE_DIR='.(Platform::isWindows() ? 'nul' : '/dev/null'));
+            $_SERVER['COMPOSER_CACHE_DIR'] = Platform::isWindows() ? 'nul' : '/dev/null';
+            putenv('COMPOSER_CACHE_DIR='.$_SERVER['COMPOSER_CACHE_DIR']);
         }
 
         // switch working dir

+ 2 - 1
src/Composer/EventDispatcher/EventDispatcher.php

@@ -243,7 +243,8 @@ class EventDispatcher
                     $finder = new PhpExecutableFinder();
                     $phpPath = $finder->find(false);
                     if ($phpPath) {
-                        putenv('PHP_BINARY=' . $phpPath);
+                        $_SERVER['PHP_BINARY'] = $phpPath;
+                        putenv('PHP_BINARY=' . $_SERVER['PHP_BINARY']);
                     }
                 }
 

+ 5 - 0
src/Composer/IO/BaseIO.php

@@ -115,6 +115,7 @@ abstract class BaseIO implements IOInterface
         $gitlabOauth = $config->get('gitlab-oauth') ?: array();
         $gitlabToken = $config->get('gitlab-token') ?: array();
         $httpBasic = $config->get('http-basic') ?: array();
+        $bearerToken = $config->get('bearer') ?: array();
 
         // reload oauth tokens from config if available
 
@@ -142,6 +143,10 @@ abstract class BaseIO implements IOInterface
             $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']);
         }
 
+        foreach ($bearerToken as $domain => $token) {
+            $this->checkAndSetAuthentication($domain, $token, 'bearer');
+        }
+
         // setup process timeout
         ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
     }

+ 2 - 2
src/Composer/Installer.php

@@ -219,8 +219,8 @@ class Installer
         }
 
         if ($this->runScripts) {
-            $devMode = (int) $this->devMode;
-            putenv("COMPOSER_DEV_MODE=$devMode");
+            $_SERVER['COMPOSER_DEV_MODE'] = (int) $this->devMode;
+            putenv('COMPOSER_DEV_MODE='.$_SERVER['COMPOSER_DEV_MODE']);
 
             // dispatch pre event
             // should we treat this more strictly as running an update and then running an install, triggering events multiple times?

+ 8 - 2
src/Composer/Repository/VcsRepository.php

@@ -23,6 +23,7 @@ use Composer\EventDispatcher\EventDispatcher;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\HttpDownloader;
 use Composer\Util\Url;
+use Composer\Semver\Constraint\Constraint;
 use Composer\IO\IOInterface;
 use Composer\Config;
 
@@ -381,7 +382,12 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
     private function validateBranch($branch)
     {
         try {
-            return $this->versionParser->normalizeBranch($branch);
+            $normalizedBranch = $this->versionParser->normalizeBranch($branch);
+
+            // validate that the branch name has no weird characters conflicting with constraints
+            $this->versionParser->parseConstraints($normalizedBranch);
+
+            return $normalizedBranch;
         } catch (\Exception $e) {
         }
 
@@ -421,7 +427,7 @@ class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInt
                 $this->io->overwriteError($msg, false);
             }
 
-            if ($existingPackage = $this->findPackage($cachedPackage['name'], $cachedPackage['version_normalized'])) {
+            if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) {
                 if ($isVeryVerbose) {
                     $this->io->writeError('<warning>Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally</warning>');
                 }

+ 4 - 2
src/Composer/Util/AuthHelper.php

@@ -117,7 +117,7 @@ class AuthHelper
             $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
             $gitLabUtil = new GitLab($this->io, $this->config, null);
 
-            if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token'), true)) {
+            if ($this->io->hasAuthentication($origin) && ($auth = $this->io->getAuthentication($origin)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token', 'oauth2'), true)) {
                 throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
             }
 
@@ -196,7 +196,9 @@ class AuthHelper
         if ($this->io->hasAuthentication($origin)) {
             $authenticationDisplayMessage = null;
             $auth = $this->io->getAuthentication($origin);
-            if ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
+            if ($auth['password'] === 'bearer') {
+                $headers[] = 'Authorization: Bearer '.$auth['username'];
+            } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
                 $headers[] = 'Authorization: token '.$auth['username'];
                 $authenticationDisplayMessage = 'Using GitHub token authentication';
             } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {

+ 6 - 4
src/Composer/Util/Filesystem.php

@@ -207,7 +207,7 @@ class Filesystem
                 usleep(350000);
                 $unlinked = @$this->unlinkImplementation($path);
             }
-            
+
             if (!$unlinked) {
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
@@ -238,7 +238,7 @@ class Filesystem
                 usleep(350000);
                 $deleted = @rmdir($path);
             }
-            
+
             if (!$deleted) {
                 $error = error_get_last();
                 $message = 'Could not delete '.$path.': ' . @$error['message'];
@@ -312,7 +312,9 @@ class Filesystem
         }
 
         if (!function_exists('proc_open')) {
-            return $this->copyThenRemove($source, $target);
+            $this->copyThenRemove($source, $target);
+            
+            return;
         }
 
         if (Platform::isWindows()) {
@@ -342,7 +344,7 @@ class Filesystem
             }
         }
 
-        return $this->copyThenRemove($source, $target);
+        $this->copyThenRemove($source, $target);
     }
 
     /**

+ 52 - 0
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -486,6 +486,58 @@ class AutoloadGeneratorTest extends TestCase
         $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty.");
     }
 
+    public function testNonDevAutoloadReplacesNestedRequirements()
+    {
+        $package = new Package('a', '1.0', '1.0');
+        $package->setRequires(array(
+            new Link('a', 'a/a')
+        ));
+
+        $packages = array();
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
+        $packages[] = $c = new Package('c/c', '1.0', '1.0');
+        $packages[] = $d = new Package('d/d', '1.0', '1.0');
+        $packages[] = $e = new Package('e/e', '1.0', '1.0');
+        $a->setAutoload(array('classmap' => array('src/A.php')));
+        $a->setRequires(array(
+            new Link('a/a', 'b/b')
+        ));
+        $b->setAutoload(array('classmap' => array('src/B.php')));
+        $b->setRequires(array(
+            new Link('b/b', 'e/e')
+        ));
+        $c->setAutoload(array('classmap' => array('src/C.php')));
+        $c->setReplaces(array(
+            new Link('c/c', 'b/b')
+        ));
+        $c->setRequires(array(
+            new Link('c/c', 'd/d')
+        ));
+        $d->setAutoload(array('classmap' => array('src/D.php')));
+        $e->setAutoload(array('classmap' => array('src/E.php')));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue($packages));
+
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/d/d/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/e/e/src');
+
+        file_put_contents($this->vendorDir.'/a/a/src/A.php', '<?php class A {}');
+        file_put_contents($this->vendorDir.'/b/b/src/B.php', '<?php class B {}');
+        file_put_contents($this->vendorDir.'/c/c/src/C.php', '<?php class C {}');
+        file_put_contents($this->vendorDir.'/d/d/src/D.php', '<?php class D {}');
+        file_put_contents($this->vendorDir.'/e/e/src/E.php', '<?php class E {}');
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5');
+
+        $this->assertAutoloadFiles('classmap9', $this->vendorDir.'/composer', 'classmap');
+    }
+
     public function testPharAutoload()
     {
         $package = new Package('a', '1.0', '1.0');

+ 12 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_classmap9.php

@@ -0,0 +1,12 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'A' => $vendorDir . '/a/a/src/A.php',
+    'C' => $vendorDir . '/c/c/src/C.php',
+    'D' => $vendorDir . '/d/d/src/D.php',
+);