Explorar el Código

Merge branch 'master' into 2.0

Jordi Boggiano hace 6 años
padre
commit
4517c00d40

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+### [1.8.1] 2019-01-29
+
+  * Deprecated support for non-standard package names (anything with uppercase, or no / in it). Make sure to follow the warnings if you see any to avoid problems in 2.0.
+  * Fixed some packages missing from the autoloader config when installing with --no-dev
+  * Fixed support for cloning GitLab repos using OAuth tokens instead of SSH keys
+  * Fixed metapackage installs/updates missing from output
+  * Fixed --with-dependencies / --with-all-dependencies not updating some packages in some edge cases
+  * Fixed compatibility with Symfony 4.2 deprecations
+  * Fixed temp dir not being cleaned up on download error while archiving packages
+  * Updated to latest ca-bundle
+
 ### [1.8.0] 2018-12-03
 
   * Changed `post-package-install` / `post-package-update` event to be fired *after* the lock file has been updated as opposed to before

+ 8 - 0
doc/06-config.md

@@ -234,6 +234,14 @@ github API will have a date instead of the machine hostname.
 Defaults to `["gitlab.com"]`. A list of domains of GitLab servers.
 This is used if you use the `gitlab` repository type.
 
+## use-github-api
+
+Defaults to `true`.  Similar to the `no-api` key on a specific repository,
+setting `use-github-api` to `false` will define the global behavior for all
+GitHub repositories to clone the repository as it would with any other git
+repository instead of using the GitHub API. But unlike using the `git`
+driver directly, Composer will still attempt to use GitHub's zip files.
+
 ## notify-on-install
 
 Defaults to `true`. Composer allows repositories to define a notification URL,

+ 4 - 0
res/composer-schema.json

@@ -271,6 +271,10 @@
                         "type": "string"
                     }
                 },
+                "use-github-api": {
+                    "type": "boolean",
+                    "description": "Defaults to true.  If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."
+                },
                 "archive-format": {
                     "type": "string",
                     "description": "The default archiving format when not provided on cli, defaults to \"tar\"."

+ 1 - 0
src/Composer/Command/ConfigCommand.php

@@ -302,6 +302,7 @@ EOT
         $uniqueConfigValues = array(
             'process-timeout' => array('is_numeric', 'intval'),
             'use-include-path' => array($booleanValidator, $booleanNormalizer),
+            'use-github-api' => array($booleanValidator, $booleanNormalizer),
             'preferred-install' => array(
                 function ($val) {
                     return in_array($val, array('auto', 'source', 'dist'), true);

+ 3 - 2
src/Composer/Config.php

@@ -61,6 +61,7 @@ class Config
         'archive-format' => 'tar',
         'archive-dir' => '.',
         'htaccess-protect' => true,
+        'use-github-api' => true,
         // valid keys without defaults (auth config stuff):
         // bitbucket-oauth
         // github-oauth
@@ -323,10 +324,10 @@ class Config
 
             case 'disable-tls':
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
-
             case 'secure-http':
                 return $this->config[$key] !== 'false' && (bool) $this->config[$key];
-
+            case 'use-github-api':
+                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
             default:
                 if (!isset($this->config[$key])) {
                     return null;

+ 2 - 2
src/Composer/Downloader/FileDownloader.php

@@ -251,8 +251,8 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
     public function update(PackageInterface $initial, PackageInterface $target, $path)
     {
         $name = $target->getName();
-        $from = $initial->getPrettyVersion();
-        $to = $target->getPrettyVersion();
+        $from = $initial->getFullPrettyVersion();
+        $to = $target->getFullPrettyVersion();
 
         $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
         $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

+ 2 - 2
src/Composer/Factory.php

@@ -349,7 +349,7 @@ class Factory
         // load package
         $parser = new VersionParser;
         $guesser = new VersionGuesser($config, new ProcessExecutor($io), $parser);
-        $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser);
+        $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
         $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
         $composer->setPackage($package);
 
@@ -542,7 +542,7 @@ class Factory
         $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
         $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
         $im->addInstaller(new Installer\PluginInstaller($io, $composer));
-        $im->addInstaller(new Installer\MetapackageInstaller());
+        $im->addInstaller(new Installer\MetapackageInstaller($io));
     }
 
     /**

+ 19 - 0
src/Composer/Installer/MetapackageInstaller.php

@@ -14,6 +14,8 @@ namespace Composer\Installer;
 
 use Composer\Repository\InstalledRepositoryInterface;
 use Composer\Package\PackageInterface;
+use Composer\Package\Version\VersionParser;
+use Composer\IO\IOInterface;
 
 /**
  * Metapackage installation manager.
@@ -22,6 +24,13 @@ use Composer\Package\PackageInterface;
  */
 class MetapackageInstaller implements InstallerInterface
 {
+    private $io;
+
+    public function __construct(IOInterface $io)
+    {
+        $this->io = $io;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -51,6 +60,8 @@ class MetapackageInstaller implements InstallerInterface
      */
     public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
+        $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
+
         $repo->addPackage(clone $package);
     }
 
@@ -63,6 +74,12 @@ class MetapackageInstaller implements InstallerInterface
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
         }
 
+        $name = $target->getName();
+        $from = $initial->getFullPrettyVersion();
+        $to = $target->getFullPrettyVersion();
+        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
+        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");
+
         $repo->removePackage($initial);
         $repo->addPackage(clone $target);
     }
@@ -76,6 +93,8 @@ class MetapackageInstaller implements InstallerInterface
             throw new \InvalidArgumentException('Package is not installed: '.$package);
         }
 
+        $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
+
         $repo->removePackage($package);
     }
 

+ 24 - 1
src/Composer/Package/Loader/RootPackageLoader.php

@@ -15,6 +15,7 @@ namespace Composer\Package\Loader;
 use Composer\Package\BasePackage;
 use Composer\Package\AliasPackage;
 use Composer\Config;
+use Composer\IO\IOInterface;
 use Composer\Package\RootPackageInterface;
 use Composer\Repository\RepositoryFactory;
 use Composer\Package\Version\VersionGuesser;
@@ -46,13 +47,19 @@ class RootPackageLoader extends ArrayLoader
      */
     private $versionGuesser;
 
-    public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null)
+    /**
+     * @var IOInterface
+     */
+    private $io;
+
+    public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null, IOInterface $io = null)
     {
         parent::__construct($parser);
 
         $this->manager = $manager;
         $this->config = $config;
         $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser);
+        $this->io = $io;
     }
 
     /**
@@ -65,6 +72,10 @@ class RootPackageLoader extends ArrayLoader
     {
         if (!isset($config['name'])) {
             $config['name'] = '__root__';
+        } elseif ($this->io) {
+            if ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
+                $this->io->writeError('<warning>Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
+            }
         }
         $autoVersioned = false;
         if (!isset($config['version'])) {
@@ -131,6 +142,18 @@ class RootPackageLoader extends ArrayLoader
             }
         }
 
+        if ($this->io) {
+            foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
+                if (isset($config[$linkType])) {
+                    foreach ($config[$linkType] as $linkName => $constraint) {
+                        if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
+                            $this->io->writeError('<warning>Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
+                        }
+                    }
+                }
+            }
+        }
+
         if (isset($links[$config['name']])) {
             throw new \InvalidArgumentException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
                         'Did you accidentally name your root package after an external package?', $config['name']));

+ 32 - 0
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -336,6 +336,38 @@ class ValidatingArrayLoader implements LoaderInterface
         return $this->errors;
     }
 
+    public static function hasPackageNamingError($name, $isLink = false)
+    {
+        if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
+            return;
+        }
+
+        if (!preg_match('{^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$}iD', $name)) {
+            return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*".';
+        }
+
+        $reservedNames = array('nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9');
+        $bits = explode('/', strtolower($name));
+        if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
+            return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
+        }
+
+        if (preg_match('{\.json$}', $name)) {
+            return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.';
+        }
+
+        if (preg_match('{[A-Z]}', $name)) {
+            if ($isLink) {
+                return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.';
+            }
+
+            $suggestName = preg_replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
+            $suggestName = strtolower($suggestName);
+
+            return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.';
+        }
+    }
+
     private function validateRegex($property, $regex, $mandatory = false)
     {
         if (!$this->validateString($property, $mandatory)) {

+ 1 - 1
src/Composer/Repository/PlatformRepository.php

@@ -24,7 +24,7 @@ use Composer\XdebugHandler\XdebugHandler;
  */
 class PlatformRepository extends ArrayRepository
 {
-    const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[^/ ]+)$}i';
+    const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:-?[a-z0-9]+)*)$}iD';
 
     private $versionParser;
 

+ 1 - 1
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -58,7 +58,7 @@ class GitHubDriver extends VcsDriver
         }
         $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
 
-        if (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api']) {
+        if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
             $this->setupGitDriver($this->url);
 
             return;

+ 27 - 0
src/Composer/Util/Git.php

@@ -153,6 +153,28 @@ class Git
                         return;
                     }
                 }
+            } elseif (preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
+                if (!$this->io->hasAuthentication($match[2])) {
+                    $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
+                    $message = 'Cloning failed, enter your GitLab credentials to access private repos';
+
+                    if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) {
+                        $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message);
+                    }
+                }
+
+                if ($this->io->hasAuthentication($match[2])) {
+                    $auth = $this->io->getAuthentication($match[2]);
+                    if($auth['password'] === 'private-token' || $auth['password'] === 'oauth2') {
+                        $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode($auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
+                    } else {
+                        $authUrl = $match[1] . '://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[2] . '/' . $match[3];
+                    }
+                    $command = call_user_func($commandCallable, $authUrl);
+                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
+                        return;
+                    }
+                }
             } elseif ($this->isAuthenticationFailure($url, $match)) { // private non-github repo that failed to authenticate
                 if (strpos($match[2], '@')) {
                     list($authParts, $match[2]) = explode('@', $match[2], 2);
@@ -304,6 +326,11 @@ class Git
         return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
     }
 
+    public static function getGitLabDomainsRegex(Config $config)
+    {
+        return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
+    }
+
     public static function sanitizeUrl($message)
     {
         return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {

+ 2 - 2
tests/Composer/Test/Downloader/FileDownloaderTest.php

@@ -223,7 +223,7 @@ class FileDownloaderTest extends TestCase
     {
         $oldPackage = $this->getMock('Composer\Package\PackageInterface');
         $oldPackage->expects($this->once())
-            ->method('getPrettyVersion')
+            ->method('getFullPrettyVersion')
             ->will($this->returnValue('1.2.0'));
         $oldPackage->expects($this->once())
             ->method('getVersion')
@@ -231,7 +231,7 @@ class FileDownloaderTest extends TestCase
 
         $newPackage = $this->getMock('Composer\Package\PackageInterface');
         $newPackage->expects($this->once())
-            ->method('getPrettyVersion')
+            ->method('getFullPrettyVersion')
             ->will($this->returnValue('1.0.0'));
         $newPackage->expects($this->once())
             ->method('getVersion')

+ 4 - 4
tests/Composer/Test/Fixtures/installer/github-issues-4319.test

@@ -13,12 +13,12 @@ Present a clear error message when config.platform.php version results in a conf
         {
             "type": "package",
             "package": [
-                { "name": "a", "version": "1.0.0", "require": { "php": "5.5" } }
+                { "name": "a/a", "version": "1.0.0", "require": { "php": "5.5" } }
             ]
         }
     ],
     "require": {
-        "a": "~1.0"
+        "a/a": "~1.0"
     },
     "config": {
         "platform": {
@@ -36,8 +36,8 @@ Updating dependencies (including require-dev)
 Your requirements could not be resolved to an installable set of packages.
 
   Problem 1
-    - Installation request for a ~1.0 -> satisfiable by a[1.0.0].
-    - a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement.
+    - Installation request for a/a ~1.0 -> satisfiable by a/a[1.0.0].
+    - a/a 1.0.0 requires php 5.5 -> your PHP version (%s) overridden by "config.platform.php" version (5.3) does not satisfy that requirement.
 
 --EXPECT--
 

+ 11 - 11
tests/Composer/Test/Fixtures/installer/github-issues-4795-2.test

@@ -11,27 +11,27 @@ that are also a root package, when that root package is also explicitly whitelis
         {
             "type": "package",
             "package": [
-                { "name": "a", "version": "1.0.0" },
-                { "name": "a", "version": "1.1.0" },
-                { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
-                { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
+                { "name": "a/a", "version": "1.0.0" },
+                { "name": "a/a", "version": "1.1.0" },
+                { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
+                { "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } }
             ]
         }
     ],
     "require": {
-        "a": "~1.0",
-        "b": "~1.0"
+        "a/a": "~1.0",
+        "b/b": "~1.0"
     }
 }
 
 --INSTALLED--
 [
-    { "name": "a", "version": "1.0.0" },
-    { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
+    { "name": "a/a", "version": "1.0.0" },
+    { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
 ]
 
 --RUN--
-update a b --with-dependencies
+update a/a b/b --with-dependencies
 
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
@@ -41,5 +41,5 @@ Writing lock file
 Generating autoload files
 
 --EXPECT--
-Updating a (1.0.0) to a (1.1.0)
-Updating b (1.0.0) to b (1.1.0)
+Updating a/a (1.0.0) to a/a (1.1.0)
+Updating b/b (1.0.0) to b/b (1.1.0)

+ 10 - 10
tests/Composer/Test/Fixtures/installer/github-issues-4795.test

@@ -11,30 +11,30 @@ dependency of one the requirements that is whitelisted for update.
         {
             "type": "package",
             "package": [
-                { "name": "a", "version": "1.0.0" },
-                { "name": "a", "version": "1.1.0" },
-                { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
-                { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
+                { "name": "a/a", "version": "1.0.0" },
+                { "name": "a/a", "version": "1.1.0" },
+                { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
+                { "name": "b/b", "version": "1.1.0", "require": { "a/b": "~1.1" } }
             ]
         }
     ],
     "require": {
-        "a": "~1.0",
-        "b": "~1.0"
+        "a/a": "~1.0",
+        "b/b": "~1.0"
     }
 }
 
 --INSTALLED--
 [
-    { "name": "a", "version": "1.0.0" },
-    { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
+    { "name": "a/a", "version": "1.0.0" },
+    { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
 ]
 
 --RUN--
-update b --with-dependencies
+update b/b --with-dependencies
 
 --EXPECT-OUTPUT--
-<warning>Dependency "a" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>
+<warning>Dependency "a/a" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>
 Loading composer repositories with package information
 Updating dependencies (including require-dev)
 Nothing to install or update

+ 16 - 16
tests/Composer/Test/Fixtures/installer/solver-problems.test

@@ -8,30 +8,30 @@ Test the error output of solver problems.
             "package": [
                 { "name": "unstable/package", "version": "2.0.0-alpha" },
                 { "name": "unstable/package", "version": "1.0.0" },
-                { "name": "requirer", "version": "1.0.0", "require": {"dependency": "1.0.0" } },
-                { "name": "dependency", "version": "2.0.0" },
-                { "name": "dependency", "version": "1.0.0" },
-                { "name": "stable-requiree-excluded", "version": "1.0.1" },
-                { "name": "stable-requiree-excluded", "version": "1.0.0" }
+                { "name": "requirer/pkg", "version": "1.0.0", "require": {"dependency/pkg": "1.0.0" } },
+                { "name": "dependency/pkg", "version": "2.0.0" },
+                { "name": "dependency/pkg", "version": "1.0.0" },
+                { "name": "stable-requiree-excluded/pkg", "version": "1.0.1" },
+                { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
             ]
         }
     ],
     "require": {
         "unstable/package": "2.*",
-        "bogus": "1.*",
-        "requirer": "1.*",
-        "dependency": "2.*",
-        "stable-requiree-excluded": "1.0.1"
+        "bogus/pkg": "1.*",
+        "requirer/pkg": "1.*",
+        "dependency/pkg": "2.*",
+        "stable-requiree-excluded/pkg": "1.0.1"
     }
 }
 
 --INSTALLED--
 [
-    { "name": "stable-requiree-excluded", "version": "1.0.0" }
+    { "name": "stable-requiree-excluded/pkg", "version": "1.0.0" }
 ]
 
 --RUN--
-update unstable/package requirer dependency
+update unstable/package requirer/pkg dependency/pkg
 
 --EXPECT-EXIT-CODE--
 2
@@ -44,14 +44,14 @@ Your requirements could not be resolved to an installable set of packages.
   Problem 1
     - The requested package unstable/package could not be found in any version, there may be a typo in the package name.
   Problem 2
-    - The requested package bogus could not be found in any version, there may be a typo in the package name.
+    - The requested package bogus/pkg could not be found in any version, there may be a typo in the package name.
   Problem 3
-    - The requested package stable-requiree-excluded 1.0.1 exists as stable-requiree-excluded[1.0.0] but these are rejected by your constraint.
+    - The requested package stable-requiree-excluded/pkg 1.0.1 exists as stable-requiree-excluded/pkg[1.0.0] but these are rejected by your constraint.
   Problem 4
-    - The requested package stable-requiree-excluded (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded[1.0.0] but these conflict with your requirements or minimum-stability.
+    - The requested package stable-requiree-excluded/pkg (installed at 1.0.0, required as 1.0.1) is satisfiable by stable-requiree-excluded/pkg[1.0.0] but these conflict with your requirements or minimum-stability.
   Problem 5
-    - Installation request for requirer 1.* -> satisfiable by requirer[1.0.0].
-    - requirer 1.0.0 requires dependency 1.0.0 -> no matching package found.
+    - Installation request for requirer/pkg 1.* -> satisfiable by requirer/pkg[1.0.0].
+    - requirer/pkg 1.0.0 requires dependency/pkg 1.0.0 -> no matching package found.
 
 Potential causes:
  - A typo in the package name

+ 11 - 11
tests/Composer/Test/Fixtures/installer/update-with-all-dependencies.test

@@ -10,27 +10,27 @@ When `--with-all-dependencies` is used, Composer\Installer::whitelistUpdateDepen
         {
             "type": "package",
             "package": [
-                { "name": "a", "version": "1.0.0" },
-                { "name": "a", "version": "1.1.0" },
-                { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } },
-                { "name": "b", "version": "1.1.0", "require": { "a": "~1.1" } }
+                { "name": "a/a", "version": "1.0.0" },
+                { "name": "a/a", "version": "1.1.0" },
+                { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } },
+                { "name": "b/b", "version": "1.1.0", "require": { "a/a": "~1.1" } }
             ]
         }
     ],
     "require": {
-        "a": "~1.0",
-        "b": "~1.0"
+        "a/a": "~1.0",
+        "b/b": "~1.0"
     }
 }
 
 --INSTALLED--
 [
-    { "name": "a", "version": "1.0.0" },
-    { "name": "b", "version": "1.0.0", "require": { "a": "~1.0" } }
+    { "name": "a/a", "version": "1.0.0" },
+    { "name": "b/b", "version": "1.0.0", "require": { "a/a": "~1.0" } }
 ]
 
 --RUN--
-update b --with-all-dependencies
+update b/b --with-all-dependencies
 
 --EXPECT-OUTPUT--
 Loading composer repositories with package information
@@ -40,5 +40,5 @@ Writing lock file
 Generating autoload files
 
 --EXPECT--
-Updating a (1.0.0) to a (1.1.0)
-Updating b (1.0.0) to b (1.1.0)
+Updating a/a (1.0.0) to a/a (1.1.0)
+Updating b/b (1.0.0) to b/b (1.1.0)

+ 7 - 1
tests/Composer/Test/Installer/MetapackageInstallerTest.php

@@ -27,7 +27,7 @@ class MetapackageInstallerTest extends TestCase
 
         $this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
 
-        $this->installer = new MetapackageInstaller();
+        $this->installer = new MetapackageInstaller($this->io);
     }
 
     public function testInstall()
@@ -45,7 +45,13 @@ class MetapackageInstallerTest extends TestCase
     public function testUpdate()
     {
         $initial = $this->createPackageMock();
+        $initial->expects($this->once())
+            ->method('getVersion')
+            ->will($this->returnValue('1.0.0'));
         $target = $this->createPackageMock();
+        $target->expects($this->once())
+            ->method('getVersion')
+            ->will($this->returnValue('1.0.1'));
 
         $this->repository
             ->expects($this->exactly(2))