Browse Source

resolve conflict

till 13 years ago
parent
commit
5fdd09104f
70 changed files with 1762 additions and 614 deletions
  1. 3 0
      CHANGELOG.md
  2. 17 4
      doc/04-schema.md
  3. 3 1
      doc/05-repositories.md
  4. 23 0
      doc/articles/create-projects.md
  5. 4 0
      res/composer-schema.json
  6. 60 34
      src/Composer/Autoload/AutoloadGenerator.php
  7. 22 0
      src/Composer/Autoload/ClassLoader.php
  8. 134 0
      src/Composer/Autoload/ClassMapGenerator.php
  9. 134 0
      src/Composer/Command/CreateProjectCommand.php
  10. 11 237
      src/Composer/Command/InstallCommand.php
  11. 38 11
      src/Composer/Command/SearchCommand.php
  12. 36 11
      src/Composer/Command/ShowCommand.php
  13. 12 21
      src/Composer/Command/UpdateCommand.php
  14. 1 0
      src/Composer/Compiler.php
  15. 2 1
      src/Composer/Console/Application.php
  16. 0 2
      src/Composer/Downloader/FileDownloader.php
  17. 44 22
      src/Composer/Downloader/GitDownloader.php
  18. 2 1
      src/Composer/Downloader/HgDownloader.php
  19. 20 0
      src/Composer/Downloader/TransportException.php
  20. 5 2
      src/Composer/Factory.php
  21. 20 10
      src/Composer/IO/ConsoleIO.php
  22. 426 0
      src/Composer/Installer.php
  23. 111 0
      src/Composer/Installer/ProjectInstaller.php
  24. 8 14
      src/Composer/Json/JsonFile.php
  25. 6 4
      src/Composer/Repository/PlatformRepository.php
  26. 5 19
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  27. 2 16
      src/Composer/Repository/Vcs/GitDriver.php
  28. 6 20
      src/Composer/Repository/Vcs/GitHubDriver.php
  29. 7 21
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  30. 2 16
      src/Composer/Repository/Vcs/HgDriver.php
  31. 3 20
      src/Composer/Repository/Vcs/SvnDriver.php
  32. 16 1
      src/Composer/Repository/Vcs/VcsDriver.php
  33. 91 58
      src/Composer/Repository/VcsRepository.php
  34. 6 4
      src/Composer/Util/Filesystem.php
  35. 12 6
      src/Composer/Util/RemoteFilesystem.php
  36. 35 0
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  37. 83 0
      tests/Composer/Test/Autoload/ClassMapGeneratorTest.php
  38. 8 0
      tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php
  39. 8 0
      tests/Composer/Test/Autoload/Fixtures/Namespaced/Baz.php
  40. 8 0
      tests/Composer/Test/Autoload/Fixtures/Namespaced/Foo.php
  41. 6 0
      tests/Composer/Test/Autoload/Fixtures/Pearlike/Bar.php
  42. 6 0
      tests/Composer/Test/Autoload/Fixtures/Pearlike/Baz.php
  43. 6 0
      tests/Composer/Test/Autoload/Fixtures/Pearlike/Foo.php
  44. 3 2
      tests/Composer/Test/Autoload/Fixtures/autoload_main.php
  45. 3 2
      tests/Composer/Test/Autoload/Fixtures/autoload_main2.php
  46. 3 2
      tests/Composer/Test/Autoload/Fixtures/autoload_main3.php
  47. 1 0
      tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php
  48. 1 0
      tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php
  49. 8 0
      tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php
  50. 8 0
      tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Foo.php
  51. 6 0
      tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Bar.php
  52. 6 0
      tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Foo.php
  53. 8 0
      tests/Composer/Test/Autoload/Fixtures/classmap/SomeClass.php
  54. 8 0
      tests/Composer/Test/Autoload/Fixtures/classmap/SomeInterface.php
  55. 8 0
      tests/Composer/Test/Autoload/Fixtures/classmap/SomeParent.php
  56. 11 0
      tests/Composer/Test/Autoload/Fixtures/classmap/multipleNs.php
  57. 3 0
      tests/Composer/Test/Autoload/Fixtures/classmap/notAClass.php
  58. 1 0
      tests/Composer/Test/Autoload/Fixtures/classmap/notPhpFile.md
  59. 6 0
      tests/Composer/Test/Autoload/Fixtures/classmap/sameNsMultipleClasses.php
  60. 62 0
      tests/Composer/Test/ComposerTest.php
  61. 16 0
      tests/Composer/Test/Downloader/ArchiveDownloaderTest.php
  62. 12 12
      tests/Composer/Test/Downloader/GitDownloaderTest.php
  63. 2 2
      tests/Composer/Test/Downloader/HgDownloaderTest.php
  64. 17 17
      tests/Composer/Test/IO/ConsoleIOTest.php
  65. 84 0
      tests/Composer/Test/IO/NullIOTest.php
  66. 15 5
      tests/Composer/Test/Json/JsonFileTest.php
  67. 13 13
      tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
  68. 1 1
      tests/Composer/Test/Repository/VcsRepositoryTest.php
  69. 2 0
      tests/Composer/Test/Util/FilesystemTest.php
  70. 2 2
      tests/Composer/Test/Util/RemoteFilesystemTest.php

+ 3 - 0
CHANGELOG.md

@@ -1,8 +1,11 @@
 * 1.0.0-alpha2
 
+  * Added `create-project` command to install a project from scratch with composer
+  * Added automated `classmap` autoloading support for non-PSR-0 compliant projects
   * Git clones from GitHub automatically select between git/https/http protocols
   * Enhanced `validate` command to give more feedback
   * Added "file" downloader type to download plain files
+  * Added support for authentication with svn repositories
   * Dependency on filter_var is now optional
   * Various robustness & error handling improvements
 

+ 17 - 4
doc/04-schema.md

@@ -183,9 +183,10 @@ Optional.
 
 Autoload mapping for a PHP autoloader.
 
-Currently only [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
-autoloading is supported. Under the
-`psr-0` key you define a mapping from namespaces to paths, relative to the
+Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
+autoloading and ClassMap generation are supported.
+
+Under the `psr-0` key you define a mapping from namespaces to paths, relative to the
 package root.
 
 Example:
@@ -198,6 +199,18 @@ Example:
 
 Optional, but it is highly recommended that you follow PSR-0 and use this.
 
+You can use the classmap generation support to define autoloading for all libraries
+that do not follow "PSR-0". To configure this you specify all directories
+to search for classes.
+
+Example:
+
+    {
+        "autoload: {
+            "classmap": ["src/", "lib/"]
+        }
+    }
+
 ## target-dir
 
 Defines the installation target.
@@ -389,4 +402,4 @@ See (Vendor Bins)[articles/vendor-bins.md] for more details.
 
 Optional.
 
-← [Command-line interface](03-cli.md)  |  [Repositories](05-repositories.md) →
+← [Command-line interface](03-cli.md)  |  [Repositories](05-repositories.md) →

+ 3 - 1
doc/05-repositories.md

@@ -138,7 +138,9 @@ VCS repository provides `dist`s for them that fetch the packages as zips.
 * **GitHub:** [github.com](https://github.com) (Git)
 * **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial)
 
-The VCS driver to be used is detected automatically based on the URL.
+The VCS driver to be used is detected automatically based on the URL. However,
+should you need to specify one for whatever reason, you can use `git`, `svn` or
+`hg` as the repository type instead of `vcs`.
 
 ### PEAR
 

+ 23 - 0
doc/articles/create-projects.md

@@ -0,0 +1,23 @@
+# Create Projects
+
+You can use Composer to create new projects from an existing package.
+There are several applications for this:
+
+1. You can deploy application packages.
+2. You can check out any package and start developing on patches for example.
+3. Projects with multiple developers can use this feature to bootstrap the initial application for development.
+
+To create a new project using composer you can use the "create-project" command.
+Pass it a package name, and the directory to create the project in. You can also
+provide a version as third argument, otherwise the latest version is used.
+
+The directory is not allowed to exist, it will be created during installation.
+
+    php composer.phar create-project doctrine/orm path 2.2.0
+
+By default the command checks for the packages on packagist.org. To change this behavior
+you can use the --repository-url parameter and either point it to an HTTP url
+for your own packagist repository or to a packages.json file.
+
+If you want to get a development version of the code directly checked out
+from version control you have to add the --prefer-source parameter.

+ 4 - 0
res/composer-schema.json

@@ -127,6 +127,10 @@
                     "type": "object",
                     "description": "This is a hash of namespaces (keys) and the directories they can be found into (values) by the autoloader.",
                     "additionalProperties": true
+                },
+                "classmap": {
+                    "type": "array",
+                    "description": "This is an array of directories that contain classes to be included in the class-map generation process."
                 }
             }
         },

+ 60 - 34
src/Composer/Autoload/AutoloadGenerator.php

@@ -44,6 +44,11 @@ return call_user_func(function() {
         $loader->add($namespace, $path);
     }
 
+    $classMap = require __DIR__.'/autoload_classmap.php';
+    if ($classMap) {
+        $loader->addClassMap($classMap);
+    }
+
     $loader->register();
 
     return $loader;
@@ -56,12 +61,16 @@ EOF;
         $relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath);
         $vendorDirCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
 
+        $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
+        $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir);
+
         $namespacesFile = <<<EOF
 <?php
 
 // autoload_namespace.php generated by Composer
 
 \$vendorDir = $vendorDirCode;
+\$baseDir = $appBaseDir;
 
 return array(
 
@@ -70,48 +79,65 @@ EOF;
         $packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getPackages());
         $autoloads = $this->parseAutoloads($packageMap);
 
-        $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
-        $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir);
-
-        if (isset($autoloads['psr-0'])) {
-            foreach ($autoloads['psr-0'] as $namespace => $paths) {
-                $exportedPaths = array();
-                foreach ($paths as $path) {
-                    $path = strtr($path, '\\', '/');
-                    $baseDir = '';
-                    if (!$filesystem->isAbsolutePath($path)) {
-                        // vendor dir == working dir
-                        if (preg_match('{^(\./?)?$}', $relVendorPath)) {
-                            $path = '/'.$path;
-                            $baseDir = '$vendorDir . ';
-                        } elseif (strpos($path, $relVendorPath) === 0) {
-                            // path starts with vendor dir
-                            $path = substr($path, strlen($relVendorPath));
-                            $baseDir = '$vendorDir . ';
-                        } else {
-                            $path = '/'.$path;
-                            $baseDir = $appBaseDir . ' . ';
-                        }
-                    } elseif (strpos($path, $vendorPath) === 0) {
-                        $path = substr($path, strlen($vendorPath));
+        foreach ($autoloads['psr-0'] as $namespace => $paths) {
+            $exportedPaths = array();
+            foreach ($paths as $path) {
+                $path = strtr($path, '\\', '/');
+                $baseDir = '';
+                if (!$filesystem->isAbsolutePath($path)) {
+                    // vendor dir == working dir
+                    if (preg_match('{^(\./?)?$}', $relVendorPath)) {
+                        $path = '/'.$path;
+                        $baseDir = '$vendorDir . ';
+                    } elseif (strpos($path, $relVendorPath) === 0) {
+                        // path starts with vendor dir
+                        $path = substr($path, strlen($relVendorPath));
                         $baseDir = '$vendorDir . ';
+                    } else {
+                        $path = '/'.$path;
+                        $baseDir = '$baseDir . ';
                     }
-                    $exportedPaths[] = $baseDir.var_export($path, true);
-                }
-                $exportedPrefix = var_export($namespace, true);
-                $namespacesFile .= "    $exportedPrefix => ";
-                if (count($exportedPaths) > 1) {
-                    $namespacesFile .= "array(".implode(', ',$exportedPaths)."),\n";
-                } else {
-                    $namespacesFile .= $exportedPaths[0].",\n";
+                } elseif (strpos($path, $vendorPath) === 0) {
+                    $path = substr($path, strlen($vendorPath));
+                    $baseDir = '$vendorDir . ';
                 }
+                $exportedPaths[] = $baseDir.var_export($path, true);
+            }
+            $exportedPrefix = var_export($namespace, true);
+            $namespacesFile .= "    $exportedPrefix => ";
+            if (count($exportedPaths) > 1) {
+                $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n";
+            } else {
+                $namespacesFile .= $exportedPaths[0].",\n";
             }
         }
-
         $namespacesFile .= ");\n";
 
+        $classmapFile = <<<EOF
+<?php
+
+// autoload_classmap.php generated by Composer
+
+\$vendorDir = $vendorDirCode;
+\$baseDir = $appBaseDir;
+
+return array(
+
+EOF;
+
+        // flatten array
+        $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
+        foreach ($autoloads['classmap'] as $dir) {
+            foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
+                $path = '/'.$filesystem->findShortestPath(getcwd(), $path);
+                $classmapFile .= '    '.var_export($class, true).' => $baseDir . '.var_export($path, true).",\n";
+            }
+        }
+        $classmapFile .= ");\n";
+
         file_put_contents($targetDir.'/autoload.php', $autoloadFile);
         file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
+        file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile);
         copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
     }
 
@@ -141,7 +167,7 @@ EOF;
      */
     public function parseAutoloads(array $packageMap)
     {
-        $autoloads = array();
+        $autoloads = array('classmap' => array(), 'psr-0' => array());
         foreach ($packageMap as $item) {
             list($package, $installPath) = $item;
 

+ 22 - 0
src/Composer/Autoload/ClassLoader.php

@@ -45,6 +45,7 @@ class ClassLoader
     private $prefixes = array();
     private $fallbackDirs = array();
     private $useIncludePath = false;
+    private $classMap = array();
 
     public function getPrefixes()
     {
@@ -56,6 +57,23 @@ class ClassLoader
         return $this->fallbackDirs;
     }
 
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
     /**
      * Registers a set of classes
      *
@@ -142,6 +160,10 @@ class ClassLoader
      */
     public function findFile($class)
     {
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+
         if ('\\' == $class[0]) {
             $class = substr($class, 1);
         }

+ 134 - 0
src/Composer/Autoload/ClassMapGenerator.php

@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * This file is copied from the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ * @license MIT
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassMapGenerator
+ *
+ * @author Gyula Sallai <salla016@gmail.com>
+ */
+class ClassMapGenerator
+{
+    /**
+     * Generate a class map file
+     *
+     * @param Traversable $dirs Directories or a single path to search in
+     * @param string $file The name of the class map file
+     */
+    static public function dump($dirs, $file)
+    {
+        $maps = array();
+
+        foreach ($dirs as $dir) {
+            $maps = array_merge($maps, static::createMap($dir));
+        }
+
+        file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
+    }
+
+    /**
+     * Iterate over all files in the given directory searching for classes
+     *
+     * @param Iterator|string $dir The directory to search in or an iterator
+     *
+     * @return array A class map array
+     */
+    static public function createMap($dir)
+    {
+        if (is_string($dir)) {
+            $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
+        }
+
+        $map = array();
+
+        foreach ($dir as $file) {
+            if (!$file->isFile()) {
+                continue;
+            }
+
+            $path = $file->getRealPath();
+
+            if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') {
+                continue;
+            }
+
+            $classes = self::findClasses($path);
+
+            foreach ($classes as $class) {
+                $map[$class] = $path;
+            }
+
+        }
+
+        return $map;
+    }
+
+    /**
+     * Extract the classes in the given file
+     *
+     * @param string $path The file to check
+     *
+     * @return array The found classes
+     */
+    static private function findClasses($path)
+    {
+        $contents = file_get_contents($path);
+        $tokens   = token_get_all($contents);
+        $T_TRAIT  = version_compare(PHP_VERSION, '5.4', '<') ? -1 : T_TRAIT;
+
+        $classes = array();
+
+        $namespace = '';
+        for ($i = 0, $max = count($tokens); $i < $max; $i++) {
+            $token = $tokens[$i];
+
+            if (is_string($token)) {
+                continue;
+            }
+
+            $class = '';
+
+            switch ($token[0]) {
+                case T_NAMESPACE:
+                    $namespace = '';
+                    // If there is a namespace, extract it
+                    while (($t = $tokens[++$i]) && is_array($t)) {
+                        if (in_array($t[0], array(T_STRING, T_NS_SEPARATOR))) {
+                            $namespace .= $t[1];
+                        }
+                    }
+                    $namespace .= '\\';
+                    break;
+                case T_CLASS:
+                case T_INTERFACE:
+                case $T_TRAIT:
+                    // Find the classname
+                    while (($t = $tokens[++$i]) && is_array($t)) {
+                        if (T_STRING === $t[0]) {
+                            $class .= $t[1];
+                        } elseif ($class !== '' && T_WHITESPACE == $t[0]) {
+                            break;
+                        }
+                    }
+
+                    $classes[] = ltrim($namespace . $class, '\\');
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return $classes;
+    }
+}

+ 134 - 0
src/Composer/Command/CreateProjectCommand.php

@@ -0,0 +1,134 @@
+<?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\Command;
+
+use Composer\Factory;
+use Composer\Installer;
+use Composer\Installer\ProjectInstaller;
+use Composer\IO\IOInterface;
+use Composer\Repository\ComposerRepository;
+use Composer\Repository\FilesystemRepository;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Install a package as new project into new directory.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class CreateProjectCommand extends Command
+{
+    protected function configure()
+    {
+        $this
+            ->setName('create-project')
+            ->setDescription('Create new project from a package into given directory.')
+            ->setDefinition(array(
+                new InputArgument('package', InputArgument::REQUIRED, 'Package name to be installed'),
+                new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
+                new InputArgument('version', InputArgument::OPTIONAL, 'Version, will defaults to latest'),
+                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
+                new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'Pick a different repository url to look for the package.'),
+            ))
+            ->setHelp(<<<EOT
+The <info>create-project</info> command creates a new project from a given
+package into a new directory. You can use this command to bootstrap new
+projects or setup a clean version-controlled installation
+for developers of your project.
+
+<info>php composer.phar create-project vendor/project target-directory [version]</info>
+
+To setup a developer workable version you should create the project using the source
+controlled code by appending the <info>'--prefer-source'</info> flag.
+
+To install a package from another repository repository than the default one you
+can pass the <info>'--repository-url=http://myrepository.org'</info> flag.
+
+EOT
+            )
+        ;
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $io = $this->getApplication()->getIO();
+
+        return $this->installProject(
+            $io,
+            $input->getArgument('package'),
+            $input->getArgument('directory'),
+            $input->getArgument('version'),
+            (Boolean)$input->getOption('prefer-source'),
+            $input->getOption('repository-url')
+        );
+    }
+
+    public function installProject(IOInterface $io, $packageName, $directory = null, $version = null, $preferSource = false, $repositoryUrl = null)
+    {
+        $dm = $this->createDownloadManager($io);
+        if ($preferSource) {
+            $dm->setPreferSource(true);
+        }
+
+        if (null === $repositoryUrl) {
+            $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'));
+        } elseif (".json" === substr($repositoryUrl, -5)) {
+            $sourceRepo = new FilesystemRepository($repositoryUrl);
+        } elseif (0 === strpos($repositoryUrl, 'http')) {
+            $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl));
+        } else {
+            throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url.");
+        }
+
+        $candidates = $sourceRepo->findPackages($packageName, $version);
+        if (!$candidates) {
+            throw new \InvalidArgumentException("Could not find package $packageName" . ($version ? " with version $version." : ''));
+        }
+
+        if (null === $directory) {
+            $parts = explode("/", $packageName, 2);
+            $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);
+        }
+
+        // select highest version if we have many
+        $package = $candidates[0];
+        foreach ($candidates as $candidate) {
+            if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
+                $package = $candidate;
+            }
+        }
+
+        $io->write('<info>Installing ' . $package->getName() . ' as new project.</info>', true);
+        $projectInstaller = new ProjectInstaller($directory, $dm);
+        $projectInstaller->install($package);
+
+        $io->write('<info>Created project into directory ' . $directory . '</info>', true);
+        chdir($directory);
+
+        $composer = Factory::create($io);
+        $installer = Installer::create($io, $composer);
+
+        $installer
+            ->setPreferSource($preferSource)
+            ->run();
+    }
+
+    protected function createDownloadManager(IOInterface $io)
+    {
+        $factory = new Factory();
+        return $factory->createDownloadManager($io);
+    }
+}
+

+ 11 - 237
src/Composer/Command/InstallCommand.php

@@ -12,30 +12,10 @@
 
 namespace Composer\Command;
 
-use Composer\Script\ScriptEvents;
-use Composer\Script\EventDispatcher;
-use Composer\Autoload\AutoloadGenerator;
-use Composer\Composer;
-use Composer\DependencyResolver;
-use Composer\DependencyResolver\Pool;
-use Composer\DependencyResolver\Request;
-use Composer\DependencyResolver\Operation;
-use Composer\Package\AliasPackage;
-use Composer\Package\MemoryPackage;
-use Composer\Package\Link;
-use Composer\Package\LinkConstraint\VersionConstraint;
-use Composer\Package\PackageInterface;
-use Composer\Repository\ArrayRepository;
-use Composer\Repository\CompositeRepository;
-use Composer\Repository\PlatformRepository;
-use Composer\Repository\RepositoryInterface;
+use Composer\Installer;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
-use Composer\DependencyResolver\Operation\InstallOperation;
-use Composer\DependencyResolver\Operation\UpdateOperation;
-use Composer\DependencyResolver\Solver;
-use Composer\IO\IOInterface;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -71,222 +51,16 @@ EOT
     {
         $composer = $this->getComposer();
         $io = $this->getApplication()->getIO();
-        $eventDispatcher = new EventDispatcher($composer, $io);
-
-        return $this->install(
-            $io,
-            $composer,
-            $eventDispatcher,
-            (Boolean)$input->getOption('prefer-source'),
-            (Boolean)$input->getOption('dry-run'),
-            (Boolean)$input->getOption('verbose'),
-            (Boolean)$input->getOption('no-install-recommends'),
-            (Boolean)$input->getOption('install-suggests')
-        );
-    }
-
-    public function install(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher, $preferSource = false, $dryRun = false, $verbose = false, $noInstallRecommends = false, $installSuggests = false, $update = false, RepositoryInterface $additionalInstalledRepository = null)
-    {
-        if ($dryRun) {
-            $verbose = true;
-        }
-
-        if ($preferSource) {
-            $composer->getDownloadManager()->setPreferSource(true);
-        }
-
-        $repoManager = $composer->getRepositoryManager();
-
-        // create local repo, this contains all packages that are installed in the local project
-        $localRepo = $repoManager->getLocalRepository();
-        // create installed repo, this contains all local packages + platform packages (php & extensions)
-        $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository()));
-        if ($additionalInstalledRepository) {
-            $installedRepo->addRepository($additionalInstalledRepository);
-        }
-
-        // prepare aliased packages
-        if (!$update && $composer->getLocker()->isLocked()) {
-            $aliases = $composer->getLocker()->getAliases();
-        } else {
-            $aliases = $composer->getPackage()->getAliases();
-        }
-        foreach ($aliases as $alias) {
-            foreach ($repoManager->findPackages($alias['package'], $alias['version']) as $package) {
-                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-            }
-            foreach ($repoManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) {
-                $repoManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
-                $repoManager->getLocalRepository()->removePackage($package);
-            }
-        }
-
-        // creating repository pool
-        $pool = new Pool;
-        $pool->addRepository($installedRepo);
-        foreach ($repoManager->getRepositories() as $repository) {
-            $pool->addRepository($repository);
-        }
-
-        // dispatch pre event
-        if (!$dryRun) {
-            $eventName = $update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
-            $eventDispatcher->dispatchCommandEvent($eventName);
-        }
-
-        // creating requirements request
-        $installFromLock = false;
-        $request = new Request($pool);
-        if ($update) {
-            $io->write('<info>Updating dependencies</info>');
-
-            $request->updateAll();
-
-            $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
-
-            foreach ($links as $link) {
-                $request->install($link->getTarget(), $link->getConstraint());
-            }
-        } elseif ($composer->getLocker()->isLocked()) {
-            $installFromLock = true;
-            $io->write('<info>Installing from lock file</info>');
-
-            if (!$composer->getLocker()->isFresh()) {
-                $io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
-            }
-
-            foreach ($composer->getLocker()->getLockedPackages() as $package) {
-                $version = $package->getVersion();
-                foreach ($aliases as $alias) {
-                    if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
-                        $version = $alias['alias'];
-                        break;
-                    }
-                }
-                $constraint = new VersionConstraint('=', $version);
-                $request->install($package->getName(), $constraint);
-            }
-        } else {
-            $io->write('<info>Installing dependencies</info>');
-
-            $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
-
-            foreach ($links as $link) {
-                $request->install($link->getTarget(), $link->getConstraint());
-            }
-        }
-
-        // prepare solver
-        $installationManager = $composer->getInstallationManager();
-        $policy              = new DependencyResolver\DefaultPolicy();
-        $solver              = new DependencyResolver\Solver($policy, $pool, $installedRepo);
-
-        // solve dependencies
-        $operations = $solver->solve($request);
-
-        // force dev packages to be updated to latest reference on update
-        if ($update) {
-            foreach ($localRepo->getPackages() as $package) {
-                if ($package instanceof AliasPackage) {
-                    $package = $package->getAliasOf();
-                }
-
-                // skip non-dev packages
-                if (!$package->isDev()) {
-                    continue;
-                }
-
-                // skip packages that will be updated/uninstalled
-                foreach ($operations as $operation) {
-                    if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage())
-                        || ('uninstall' === $operation->getJobType() && $package === $operation->getPackage())
-                    ) {
-                        continue 2;
-                    }
-                }
-
-                // force update
-                $newPackage = $repoManager->findPackage($package->getName(), $package->getVersion());
-                if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
-                    $operations[] = new UpdateOperation($package, $newPackage);
-                }
-            }
-        }
-
-        // anti-alias local repository to allow updates to work fine
-        foreach ($repoManager->getLocalRepository()->getPackages() as $package) {
-            if ($package instanceof AliasPackage) {
-                $repoManager->getLocalRepository()->addPackage(clone $package->getAliasOf());
-                $repoManager->getLocalRepository()->removePackage($package);
-            }
-        }
-
-        // execute operations
-        if (!$operations) {
-            $io->write('<info>Nothing to install/update</info>');
-        }
-
-        foreach ($operations as $operation) {
-            if ($verbose) {
-                $io->write((string) $operation);
-            }
-            if (!$dryRun) {
-                $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
-
-                // if installing from lock, restore dev packages' references to their locked state
-                if ($installFromLock) {
-                    $package = null;
-                    if ('update' === $operation->getJobType()) {
-                        $package = $operation->getTargetPackage();
-                    } elseif ('install' === $operation->getJobType()) {
-                        $package = $operation->getPackage();
-                    }
-                    if ($package && $package->isDev()) {
-                        $lockData = $composer->getLocker()->getLockData();
-                        foreach ($lockData['packages'] as $lockedPackage) {
-                            if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) {
-                                $package->setSourceReference($lockedPackage['source-reference']);
-                                break;
-                            }
-                        }
-                    }
-                }
-                $installationManager->execute($operation);
-
-                $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
-            }
-        }
-
-        if (!$dryRun) {
-            if ($update || !$composer->getLocker()->isLocked()) {
-                $composer->getLocker()->setLockData($localRepo->getPackages(), $aliases);
-                $io->write('<info>Writing lock file</info>');
-            }
-
-            $localRepo->write();
-
-            $io->write('<info>Generating autoload files</info>');
-            $generator = new AutoloadGenerator;
-            $generator->dump($localRepo, $composer->getPackage(), $installationManager, $installationManager->getVendorPath().'/.composer');
-
-            // dispatch post event
-            $eventName = $update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
-            $eventDispatcher->dispatchCommandEvent($eventName);
-        }
-    }
-
-    private function collectLinks(PackageInterface $package, $noInstallRecommends, $installSuggests)
-    {
-        $links = $package->getRequires();
-
-        if (!$noInstallRecommends) {
-            $links = array_merge($links, $package->getRecommends());
-        }
-
-        if ($installSuggests) {
-            $links = array_merge($links, $package->getSuggests());
-        }
+        $install = Installer::create($io, $composer);
+
+        $install
+            ->setDryRun($input->getOption('dry-run'))
+            ->setVerbose($input->getOption('verbose'))
+            ->setPreferSource($input->getOption('prefer-source'))
+            ->setInstallRecommends(!$input->getOption('no-install-recommends'))
+            ->setInstallSuggests($input->getOption('install-suggests'))
+        ;
 
-        return $links;
+        return $install->run();
     }
 }

+ 38 - 11
src/Composer/Command/SearchCommand.php

@@ -18,6 +18,8 @@ use Symfony\Component\Console\Output\OutputInterface;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\ComposerRepository;
+use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
 
 /**
  * @author Robert Schönthal <seroscho@googlemail.com>
@@ -55,27 +57,52 @@ EOT
             $repos = new CompositeRepository(array($installedRepo, new ComposerRepository(array('url' => 'http://packagist.org'))));
         }
 
-        $tokens = array_map('strtolower', $input->getArgument('tokens'));
+        $tokens = $input->getArgument('tokens');
+        $packages = array();
+
         foreach ($repos->getPackages() as $package) {
+            if ($package instanceof AliasPackage || isset($packages[$package->getName()])) {
+                continue;
+            }
+
             foreach ($tokens as $token) {
-                if (false === ($pos = strpos($package->getName(), $token))) {
+                if (!$this->matchPackage($package, $token)) {
                     continue;
                 }
 
-                if ($platformRepo->hasPackage($package)) {
-                    $type = '<info>platform: </info> ';
-                } elseif ($installedRepo->hasPackage($package)) {
-                    $type = '<info>installed:</info> ';
+                if (false !== ($pos = stripos($package->getName(), $token))) {
+                    $name = substr($package->getPrettyName(), 0, $pos)
+                        . '<highlight>' . substr($package->getPrettyName(), $pos, strlen($token)) . '</highlight>'
+                        . substr($package->getPrettyName(), $pos + strlen($token));
                 } else {
-                    $type = '<comment>available:</comment> ';
+                    $name = $package->getPrettyName();
                 }
 
-                $name = substr($package->getPrettyName(), 0, $pos)
-                    . '<highlight>' . substr($package->getPrettyName(), $pos, strlen($token)) . '</highlight>'
-                    . substr($package->getPrettyName(), $pos + strlen($token));
-                $output->writeln($type . ': ' . $name . ' <comment>' . $package->getPrettyVersion() . '</comment>');
+                $packages[$package->getName()] = array(
+                    'name' => $name,
+                    'description' => strtok($package->getDescription(), "\r\n")
+                );
                 continue 2;
             }
         }
+
+        foreach ($packages as $details) {
+            $output->writeln($details['name'] .' <comment>:</comment> '. $details['description']);
+        }
+    }
+
+    /**
+     * tries to find a token within the name/keywords/description
+     *
+     * @param PackageInterface $package
+     * @param string $token
+     * @return boolean
+     */
+    private function matchPackage(PackageInterface $package, $token)
+    {
+        return (false !== stripos($package->getName(), $token))
+            || (false !== stripos(join(',', $package->getKeywords() ?: array()), $token))
+            || (false !== stripos($package->getDescription(), $token))
+        ;
     }
 }

+ 36 - 11
src/Composer/Command/ShowCommand.php

@@ -83,15 +83,32 @@ EOT
         }
 
         // list packages
+        $packages = array();
         foreach ($repos->getPackages() as $package) {
             if ($platformRepo->hasPackage($package)) {
-                $type = '<info>platform: </info> ';
+                $type = '<info>platform</info>:';
             } elseif ($installedRepo->hasPackage($package)) {
-                $type = '<info>installed:</info> ';
+                $type = '<info>installed</info>:';
             } else {
-                $type = '<comment>available:</comment> ';
+                $type = '<comment>available</comment>:';
+            }
+            if (isset($packages[$type][$package->getName()])
+                && version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '>=')
+            ) {
+                continue;
+            }
+            $packages[$type][$package->getName()] = $package;
+        }
+
+        foreach (array('<info>platform</info>:', '<comment>available</comment>:', '<info>installed</info>:') as $type) {
+            if (isset($packages[$type])) {
+                $output->writeln($type);
+                ksort($packages[$type]);
+                foreach ($packages[$type] as $package) {
+                    $output->writeln('  '.$package->getPrettyName() .' <comment>:</comment> '. strtok($package->getDescription(), "\r\n"));
+                }
+                $output->writeln('');
             }
-            $output->writeln($type . ' ' . $package->getPrettyName() . ' ' . $package->getPrettyVersion() . '<comment> (' . $package->getVersion() . ')</comment>');
         }
     }
 
@@ -133,20 +150,26 @@ EOT
     protected function printMeta(InputInterface $input, OutputInterface $output, PackageInterface $package, RepositoryInterface $installedRepo, RepositoryInterface $repos)
     {
         $output->writeln('<info>name</info>     : ' . $package->getPrettyName());
+        $output->writeln('<info>descrip.</info> : ' . $package->getDescription());
+        $output->writeln('<info>keywords</info> : ' . join(', ', $package->getKeywords() ?: array()));
         $this->printVersions($input, $output, $package, $installedRepo, $repos);
         $output->writeln('<info>type</info>     : ' . $package->getType());
-        $output->writeln('<info>names</info>    : ' . join(', ', $package->getNames()));
+        $output->writeln('<info>license</info>  : ' . implode(', ', $package->getLicense()));
         $output->writeln('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
         $output->writeln('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
-        $output->writeln('<info>license</info>  : ' . join(', ', $package->getLicense()));
+        $output->writeln('<info>names</info>    : ' . implode(', ', $package->getNames()));
 
         if ($package->getAutoload()) {
             $output->writeln("\n<info>autoload</info>");
             foreach ($package->getAutoload() as $type => $autoloads) {
                 $output->writeln('<comment>' . $type . '</comment>');
 
-                foreach ($autoloads as $name => $path) {
-                    $output->writeln($name . ' : ' . ($path ?: '.'));
+                if ($type === 'psr-0') {
+                    foreach ($autoloads as $name => $path) {
+                        $output->writeln(($name ?: '*') . ' => ' . ($path ?: '.'));
+                    }
+                } elseif ($type === 'classmap') {
+                    $output->writeln(implode(', ', $autoloads));
                 }
             }
         }
@@ -165,10 +188,12 @@ EOT
         $versions = array();
 
         foreach ($repos->findPackages($package->getName()) as $version) {
-            $versions[$version->getPrettyVersion()] = true;
+            $versions[$version->getPrettyVersion()] = $version->getVersion();
         }
 
-        $versions = join(', ', array_keys($versions));
+        uasort($versions, 'version_compare');
+
+        $versions = implode(', ', array_keys(array_reverse($versions)));
 
         // highlight installed version
         if ($installedRepo->hasPackage($package)) {
@@ -193,4 +218,4 @@ EOT
             }
         }
     }
-}
+}

+ 12 - 21
src/Composer/Command/UpdateCommand.php

@@ -12,14 +12,7 @@
 
 namespace Composer\Command;
 
-use Composer\Autoload\AutoloadGenerator;
-use Composer\DependencyResolver;
-use Composer\DependencyResolver\Pool;
-use Composer\DependencyResolver\Request;
-use Composer\DependencyResolver\Operation;
-use Composer\Package\LinkConstraint\VersionConstraint;
-use Composer\Repository\PlatformRepository;
-use Composer\Script\EventDispatcher;
+use Composer\Installer;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
@@ -54,21 +47,19 @@ EOT
 
     protected function execute(InputInterface $input, OutputInterface $output)
     {
-        $installCommand = $this->getApplication()->find('install');
         $composer = $this->getComposer();
         $io = $this->getApplication()->getIO();
-        $eventDispatcher = new EventDispatcher($composer, $io);
+        $install = Installer::create($io, $composer);
 
-        return $installCommand->install(
-            $io,
-            $composer,
-            $eventDispatcher,
-            (Boolean)$input->getOption('prefer-source'),
-            (Boolean)$input->getOption('dry-run'),
-            (Boolean)$input->getOption('verbose'),
-            (Boolean)$input->getOption('no-install-recommends'),
-            (Boolean)$input->getOption('install-suggests'),
-            true
-        );
+        $install
+            ->setDryRun($input->getOption('dry-run'))
+            ->setVerbose($input->getOption('verbose'))
+            ->setPreferSource($input->getOption('prefer-source'))
+            ->setInstallRecommends(!$input->getOption('no-install-recommends'))
+            ->setInstallSuggests($input->getOption('install-suggests'))
+            ->setUpdate(true)
+        ;
+
+        return $install->run();
     }
 }

+ 1 - 0
src/Composer/Compiler.php

@@ -82,6 +82,7 @@ class Compiler
         $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/ClassLoader.php'));
         $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload.php'));
         $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_namespaces.php'));
+        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_classmap.php'));
         $this->addComposerBin($phar);
 
         // Stubs

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

@@ -107,6 +107,7 @@ class Application extends BaseApplication
         $this->add(new Command\DependsCommand());
         $this->add(new Command\InitCommand());
         $this->add(new Command\InstallCommand());
+        $this->add(new Command\CreateProjectCommand());
         $this->add(new Command\UpdateCommand());
         $this->add(new Command\SearchCommand());
         $this->add(new Command\ValidateCommand());
@@ -128,4 +129,4 @@ class Application extends BaseApplication
 
         return $helperSet;
     }
-}
+}

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

@@ -84,8 +84,6 @@ class FileDownloader implements DownloaderInterface
         if ($checksum && hash_file('sha1', $fileName) !== $checksum) {
             throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url.')');
         }
-
-        $this->io->write('');
     }
 
     /**

+ 44 - 22
src/Composer/Downloader/GitDownloader.php

@@ -29,24 +29,11 @@ class GitDownloader extends VcsDownloader
         $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s';
         $this->io->write("    Cloning ".$package->getSourceReference());
 
-        // github, autoswitch protocols
-        if (preg_match('{^(?:https?|git)(://github.com/.*)}', $package->getSourceUrl(), $match)) {
-            $protocols = array('git', 'https', 'http');
-            foreach ($protocols as $protocol) {
-                $url = escapeshellarg($protocol . $match[1]);
-                if (0 === $this->process->execute(sprintf($command, $url, escapeshellarg($path), $ref), $ignoredOutput)) {
-                    return;
-                }
-                $this->filesystem->removeDirectory($path);
-            }
-            throw new \RuntimeException('Failed to checkout ' . $url .' via git, https and http protocols, aborting.' . "\n\n" . $this->process->getErrorOutput());
-        } else {
-            $url = escapeshellarg($package->getSourceUrl());
-            $command = sprintf($command, $url, escapeshellarg($path), $ref);
-            if (0 !== $this->process->execute($command, $ignoredOutput)) {
-                throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
-            }
-        }
+        $commandCallable = function($url) use ($ref, $path, $command) {
+            return sprintf($command, $url, escapeshellarg($path), $ref);
+        };
+
+        $this->runCommand($commandCallable, $package->getSourceUrl(), $path);
     }
 
     /**
@@ -57,10 +44,13 @@ class GitDownloader extends VcsDownloader
         $ref = escapeshellarg($target->getSourceReference());
         $path = escapeshellarg($path);
         $this->io->write("    Checking out ".$target->getSourceReference());
-        $command = sprintf('cd %s && git fetch && git checkout %2$s && git reset --hard %2$s', $path, $ref);
-        if (0 !== $this->process->execute($command, $ignoredOutput)) {
-            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
-        }
+        $command = 'cd %s && git remote set-url origin %s && git fetch && git checkout %3$s && git reset --hard %3$s';
+
+        $commandCallable = function($url) use ($ref, $path, $command) {
+            return sprintf($command, $path, $url, $ref);
+        };
+
+        $this->runCommand($commandCallable, $target->getSourceUrl());
     }
 
     /**
@@ -77,4 +67,36 @@ class GitDownloader extends VcsDownloader
             throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes');
         }
     }
+
+    /**
+     * Runs a command doing attempts for each protocol supported by github.
+     *
+     * @param callable $commandCallable A callable building the command for the given url
+     * @param string $url
+     * @param string $path The directory to remove for each attempt (null if not needed)
+     * @throws \RuntimeException
+     */
+    protected function runCommand($commandCallable, $url, $path = null)
+    {
+        // github, autoswitch protocols
+        if (preg_match('{^(?:https?|git)(://github.com/.*)}', $url, $match)) {
+            $protocols = array('git', 'https', 'http');
+            foreach ($protocols as $protocol) {
+                $url = escapeshellarg($protocol . $match[1]);
+                if (0 === $this->process->execute(call_user_func($commandCallable, $url), $ignoredOutput)) {
+                    return;
+                }
+                if (null !== $path) {
+                    $this->filesystem->removeDirectory($path);
+                }
+            }
+            throw new \RuntimeException('Failed to checkout ' . $url .' via git, https and http protocols, aborting.' . "\n\n" . $this->process->getErrorOutput());
+        }
+
+        $url = escapeshellarg($url);
+        $command = call_user_func($commandCallable, $url);
+        if (0 !== $this->process->execute($command, $ignoredOutput)) {
+            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
+        }
+    }
 }

+ 2 - 1
src/Composer/Downloader/HgDownloader.php

@@ -37,10 +37,11 @@ class HgDownloader extends VcsDownloader
      */
     public function doUpdate(PackageInterface $initial, PackageInterface $target, $path)
     {
+        $url = escapeshellarg($target->getSourceUrl());
         $ref = escapeshellarg($target->getSourceReference());
         $path = escapeshellarg($path);
         $this->io->write("    Updating to ".$target->getSourceReference());
-        $this->process->execute(sprintf('cd %s && hg pull && hg up %s', $path, $ref), $ignoredOutput);
+        $this->process->execute(sprintf('cd %s && hg pull %s && hg up %s', $path, $url, $ref), $ignoredOutput);
     }
 
     /**

+ 20 - 0
src/Composer/Downloader/TransportException.php

@@ -0,0 +1,20 @@
+<?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\Downloader;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class TransportException extends \Exception
+{
+}

+ 5 - 2
src/Composer/Factory.php

@@ -123,8 +123,11 @@ class Factory
         $rm = new RepositoryManager($io);
         $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
         $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
-        $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
         $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
+        $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
+        $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
+        $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
+        $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
 
         return $rm;
     }
@@ -139,7 +142,7 @@ class Factory
         $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org')));
     }
 
-    protected function createDownloadManager(IOInterface $io)
+    public function createDownloadManager(IOInterface $io)
     {
         $dm = new Downloader\DownloadManager();
         $dm->setDownloader('git', new Downloader\GitDownloader($io));

+ 20 - 10
src/Composer/IO/ConsoleIO.php

@@ -31,6 +31,7 @@ class ConsoleIO implements IOInterface
     protected $authorizations = array();
     protected $lastUsername;
     protected $lastPassword;
+    protected $lastMessage;
 
     /**
      * Constructor.
@@ -60,31 +61,40 @@ class ConsoleIO implements IOInterface
     public function write($messages, $newline = true)
     {
         $this->output->write($messages, $newline);
+        $this->lastMessage = join($newline ? "\n" : '', (array) $messages);
     }
 
     /**
      * {@inheritDoc}
      */
-    public function overwrite($messages, $newline = true, $size = 80)
+    public function overwrite($messages, $newline = true, $size = null)
     {
-        for ($place = $size; $place > 0; $place--) {
-            $this->write("\x08", false);
+        // messages can be an array, let's convert it to string anyway
+        $messages = join($newline ? "\n" : '', (array) $messages);
+
+        // since overwrite is supposed to overwrite last message...
+        if (!isset($size)) {
+            // removing possible formatting of lastMessage with strip_tags
+            $size = strlen(strip_tags($this->lastMessage));
         }
+        // ...let's fill its length with backspaces
+        $this->write(str_repeat("\x08", $size), false);
 
+        // write the new message
         $this->write($messages, false);
 
-        for ($place = ($size - strlen($messages)); $place > 0; $place--) {
-            $this->write(' ', false);
-        }
-
-        // clean up the end line
-        for ($place = ($size - strlen($messages)); $place > 0; $place--) {
-            $this->write("\x08", false);
+        $fill = $size - strlen(strip_tags($messages));
+        if ($fill > 0) {
+            // whitespace whatever has left
+            $this->write(str_repeat(' ', $fill), false);
+            // move the cursor back
+            $this->write(str_repeat("\x08", $fill), false);
         }
 
         if ($newline) {
             $this->write('');
         }
+        $this->lastMessage = $messages;
     }
 
     /**

+ 426 - 0
src/Composer/Installer.php

@@ -0,0 +1,426 @@
+<?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;
+
+use Composer\Autoload\AutoloadGenerator;
+use Composer\DependencyResolver\DefaultPolicy;
+use Composer\DependencyResolver\Operation\UpdateOperation;
+use Composer\DependencyResolver\Pool;
+use Composer\DependencyResolver\Request;
+use Composer\DependencyResolver\Solver;
+use Composer\Downloader\DownloadManager;
+use Composer\Installer\InstallationManager;
+use Composer\IO\IOInterface;
+use Composer\Package\AliasPackage;
+use Composer\Package\Link;
+use Composer\Package\LinkConstraint\VersionConstraint;
+use Composer\Package\Locker;
+use Composer\Package\PackageInterface;
+use Composer\Repository\CompositeRepository;
+use Composer\Repository\PlatformRepository;
+use Composer\Repository\RepositoryInterface;
+use Composer\Repository\RepositoryManager;
+use Composer\Script\EventDispatcher;
+use Composer\Script\ScriptEvents;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Beau Simensen <beau@dflydev.com>
+ * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ */
+class Installer
+{
+    /**
+     * @var IOInterface
+     */
+    protected $io;
+
+    /**
+     * @var PackageInterface
+     */
+    protected $package;
+
+    /**
+     * @var DownloadManager
+     */
+    protected $downloadManager;
+
+    /**
+     * @var RepositoryManager
+     */
+    protected $repositoryManager;
+
+    /**
+     * @var Locker
+     */
+    protected $locker;
+
+    /**
+     * @var InstallationManager
+     */
+    protected $installationManager;
+
+    /**
+     * @var EventDispatcher
+     */
+    protected $eventDispatcher;
+
+    protected $preferSource = false;
+    protected $dryRun = false;
+    protected $verbose = false;
+    protected $installRecommends = true;
+    protected $installSuggests = false;
+    protected $update = false;
+
+    /**
+     * @var RepositoryInterface
+     */
+    protected $additionalInstalledRepository;
+
+    /**
+     * Constructor
+     *
+     * @param IOInterface $io
+     * @param PackageInterface $package
+     * @param DownloadManager $downloadManager
+     * @param RepositoryManager $repositoryManager
+     * @param Locker $locker
+     * @param InstallationManager $installationManager
+     * @param EventDispatcher $eventDispatcher
+     */
+    public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher)
+    {
+        $this->io = $io;
+        $this->package = $package;
+        $this->downloadManager = $downloadManager;
+        $this->repositoryManager = $repositoryManager;
+        $this->locker = $locker;
+        $this->installationManager = $installationManager;
+        $this->eventDispatcher = $eventDispatcher;
+    }
+
+    /**
+     * Run installation (or update)
+     */
+    public function run()
+    {
+        if ($this->dryRun) {
+            $this->verbose = true;
+        }
+
+        if ($this->preferSource) {
+            $this->downloadManager->setPreferSource(true);
+        }
+
+        // create local repo, this contains all packages that are installed in the local project
+        $localRepo = $this->repositoryManager->getLocalRepository();
+        // create installed repo, this contains all local packages + platform packages (php & extensions)
+        $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository()));
+        if ($this->additionalInstalledRepository) {
+            $installedRepo->addRepository($this->additionalInstalledRepository);
+        }
+
+        // prepare aliased packages
+        if (!$this->update && $this->locker->isLocked()) {
+            $aliases = $this->locker->getAliases();
+        } else {
+            $aliases = $this->package->getAliases();
+        }
+        foreach ($aliases as $alias) {
+            foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) {
+                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+            }
+            foreach ($this->repositoryManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) {
+                $this->repositoryManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                $this->repositoryManager->getLocalRepository()->removePackage($package);
+            }
+        }
+
+        // creating repository pool
+        $pool = new Pool;
+        $pool->addRepository($installedRepo);
+        foreach ($this->repositoryManager->getRepositories() as $repository) {
+            $pool->addRepository($repository);
+        }
+
+        // dispatch pre event
+        if (!$this->dryRun) {
+            $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
+            $this->eventDispatcher->dispatchCommandEvent($eventName);
+        }
+
+        // creating requirements request
+        $installFromLock = false;
+        $request = new Request($pool);
+        if ($this->update) {
+            $this->io->write('<info>Updating dependencies</info>');
+
+            $request->updateAll();
+
+            $links = $this->collectLinks();
+
+            foreach ($links as $link) {
+                $request->install($link->getTarget(), $link->getConstraint());
+            }
+        } elseif ($this->locker->isLocked()) {
+            $installFromLock = true;
+            $this->io->write('<info>Installing from lock file</info>');
+
+            if (!$this->locker->isFresh()) {
+                $this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
+            }
+
+            foreach ($this->locker->getLockedPackages() as $package) {
+                $version = $package->getVersion();
+                foreach ($aliases as $alias) {
+                    if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
+                        $version = $alias['alias'];
+                        break;
+                    }
+                }
+                $constraint = new VersionConstraint('=', $version);
+                $request->install($package->getName(), $constraint);
+            }
+        } else {
+            $this->io->write('<info>Installing dependencies</info>');
+
+            $links = $this->collectLinks();
+
+            foreach ($links as $link) {
+                $request->install($link->getTarget(), $link->getConstraint());
+            }
+        }
+
+        // prepare solver
+        $policy = new DefaultPolicy();
+        $solver = new Solver($policy, $pool, $installedRepo);
+
+        // solve dependencies
+        $operations = $solver->solve($request);
+
+        // force dev packages to be updated to latest reference on update
+        if ($this->update) {
+            foreach ($localRepo->getPackages() as $package) {
+                if ($package instanceof AliasPackage) {
+                    $package = $package->getAliasOf();
+                }
+
+                // skip non-dev packages
+                if (!$package->isDev()) {
+                    continue;
+                }
+
+                // skip packages that will be updated/uninstalled
+                foreach ($operations as $operation) {
+                    if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage())
+                        || ('uninstall' === $operation->getJobType() && $package === $operation->getPackage())
+                    ) {
+                        continue 2;
+                    }
+                }
+
+                // force update
+                $newPackage = $this->repositoryManager->findPackage($package->getName(), $package->getVersion());
+                if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
+                    $operations[] = new UpdateOperation($package, $newPackage);
+                }
+            }
+        }
+
+        // anti-alias local repository to allow updates to work fine
+        foreach ($this->repositoryManager->getLocalRepository()->getPackages() as $package) {
+            if ($package instanceof AliasPackage) {
+                $this->repositoryManager->getLocalRepository()->addPackage(clone $package->getAliasOf());
+                $this->repositoryManager->getLocalRepository()->removePackage($package);
+            }
+        }
+
+        // execute operations
+        if (!$operations) {
+            $this->io->write('<info>Nothing to install/update</info>');
+        }
+
+        foreach ($operations as $operation) {
+            if ($this->verbose) {
+                $this->io->write((string) $operation);
+            }
+            if (!$this->dryRun) {
+                $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
+
+                // if installing from lock, restore dev packages' references to their locked state
+                if ($installFromLock) {
+                    $package = null;
+                    if ('update' === $operation->getJobType()) {
+                        $package = $operation->getTargetPackage();
+                    } elseif ('install' === $operation->getJobType()) {
+                        $package = $operation->getPackage();
+                    }
+                    if ($package && $package->isDev()) {
+                        $lockData = $this->locker->getLockData();
+                        foreach ($lockData['packages'] as $lockedPackage) {
+                            if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) {
+                                $package->setSourceReference($lockedPackage['source-reference']);
+                                break;
+                            }
+                        }
+                    }
+                }
+                $this->installationManager->execute($operation);
+
+                $this->eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
+
+                $localRepo->write();
+            }
+        }
+
+        if (!$this->dryRun) {
+            if ($this->update || !$this->locker->isLocked()) {
+                $this->locker->setLockData($localRepo->getPackages(), $aliases);
+                $this->io->write('<info>Writing lock file</info>');
+            }
+
+            $localRepo->write();
+
+            $this->io->write('<info>Generating autoload files</info>');
+            $generator = new AutoloadGenerator;
+            $generator->dump($localRepo, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer');
+
+            // dispatch post event
+            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
+            $this->eventDispatcher->dispatchCommandEvent($eventName);
+        }
+    }
+
+    private function collectLinks()
+    {
+        $links = $this->package->getRequires();
+
+        if ($this->installRecommends) {
+            $links = array_merge($links, $this->package->getRecommends());
+        }
+
+        if ($this->installSuggests) {
+            $links = array_merge($links, $this->package->getSuggests());
+        }
+
+        return $links;
+    }
+
+    /**
+     * Create Installer
+     *
+     * @param IOInterface $io
+     * @param Composer $composer
+     * @param EventDispatcher $eventDispatcher
+     * @return Installer
+     */
+    static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null)
+    {
+        $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io);
+
+        return new static(
+            $io,
+            $composer->getPackage(),
+            $composer->getDownloadManager(),
+            $composer->getRepositoryManager(),
+            $composer->getLocker(),
+            $composer->getInstallationManager(),
+            $eventDispatcher
+        );
+    }
+
+    public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository)
+    {
+        $this->additionalInstalledRepository = $additionalInstalledRepository;
+
+        return $this;
+    }
+
+    /**
+     * wether to run in drymode or not
+     *
+     * @param boolean $dryRun
+     * @return Installer
+     */
+    public function setDryRun($dryRun=true)
+    {
+        $this->dryRun = (boolean) $dryRun;
+
+        return $this;
+    }
+
+    /**
+     * install recommend packages
+     *
+     * @param boolean $noInstallRecommends
+     * @return Installer
+     */
+    public function setInstallRecommends($installRecommends=true)
+    {
+        $this->installRecommends = (boolean) $installRecommends;
+
+        return $this;
+    }
+
+    /**
+     * also install suggested packages
+     *
+     * @param boolean $installSuggests
+     * @return Installer
+     */
+    public function setInstallSuggests($installSuggests=true)
+    {
+        $this->installSuggests = (boolean) $installSuggests;
+
+        return $this;
+    }
+
+    /**
+     * prefer source installation
+     *
+     * @param boolean $preferSource
+     * @return Installer
+     */
+    public function setPreferSource($preferSource=true)
+    {
+        $this->preferSource = (boolean) $preferSource;
+
+        return $this;
+    }
+
+    /**
+     * update packages
+     *
+     * @param boolean $update
+     * @return Installer
+     */
+    public function setUpdate($update=true)
+    {
+        $this->update = (boolean) $update;
+
+        return $this;
+    }
+
+    /**
+     * run in verbose mode
+     *
+     * @param boolean $verbose
+     * @return Installer
+     */
+    public function setVerbose($verbose=true)
+    {
+        $this->verbose = (boolean) $verbose;
+
+        return $this;
+    }
+}

+ 111 - 0
src/Composer/Installer/ProjectInstaller.php

@@ -0,0 +1,111 @@
+<?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\Installer;
+
+use Composer\DependencyResolver\Operation\OperationInterface;
+use Composer\Package\PackageInterface;
+use Composer\Downloader\DownloadManager;
+
+/**
+ * Project Installer is used to install a single package into a directory as
+ * root project.
+ *
+ * @author Benjamin Eberlei <kontakt@beberlei.de>
+ */
+class ProjectInstaller implements InstallerInterface
+{
+    private $installPath;
+    private $downloadManager;
+
+    public function __construct($installPath, DownloadManager $dm)
+    {
+        $this->installPath = $installPath;
+        $this->downloadManager = $dm;
+    }
+
+    /**
+     * Decides if the installer supports the given type
+     *
+     * @param   string  $packageType
+     * @return  Boolean
+     */
+    public function supports($packageType)
+    {
+        return true;
+    }
+
+    /**
+     * Checks that provided package is installed.
+     *
+     * @param   PackageInterface    $package    package instance
+     *
+     * @return  Boolean
+     */
+    public function isInstalled(PackageInterface $package)
+    {
+        return false;
+    }
+
+    /**
+     * Installs specific package.
+     *
+     * @param   PackageInterface    $package    package instance
+     */
+    public function install(PackageInterface $package)
+    {
+        $installPath = $this->installPath;
+        if (file_exists($installPath)) {
+            throw new \InvalidArgumentException("Project directory $installPath already exists.");
+        }
+        if (!file_exists(dirname($installPath))) {
+            throw new \InvalidArgumentException("Project root " . dirname($installPath) . " does not exist.");
+        }
+        mkdir($installPath, 0777);
+        $this->downloadManager->download($package, $installPath);
+    }
+
+    /**
+     * Updates specific package.
+     *
+     * @param   PackageInterface    $initial    already installed package version
+     * @param   PackageInterface    $target     updated version
+     *
+     * @throws  InvalidArgumentException        if $from package is not installed
+     */
+    public function update(PackageInterface $initial, PackageInterface $target)
+    {
+        throw new \InvalidArgumentException("not supported");
+    }
+
+    /**
+     * Uninstalls specific package.
+     *
+     * @param   PackageInterface    $package    package instance
+     */
+    public function uninstall(PackageInterface $package)
+    {
+        throw new \InvalidArgumentException("not supported");
+    }
+
+    /**
+     * Returns the installation path of a package
+     *
+     * @param   PackageInterface    $package
+     * @return  string path
+     */
+    public function getInstallPath(PackageInterface $package)
+    {
+        return $this->installPath;
+    }
+}
+

+ 8 - 14
src/Composer/Json/JsonFile.php

@@ -18,16 +18,6 @@ use JsonSchema\Validator;
 use Seld\JsonLint\JsonParser;
 use Composer\Util\StreamContextFactory;
 
-if (!defined('JSON_UNESCAPED_SLASHES')) {
-    define('JSON_UNESCAPED_SLASHES', 64);
-}
-if (!defined('JSON_PRETTY_PRINT')) {
-    define('JSON_PRETTY_PRINT', 128);
-}
-if (!defined('JSON_UNESCAPED_UNICODE')) {
-    define('JSON_UNESCAPED_UNICODE', 256);
-}
-
 /**
  * Reads/writes json files.
  *
@@ -39,6 +29,10 @@ class JsonFile
     const LAX_SCHEMA = 1;
     const STRICT_SCHEMA = 2;
 
+    const JSON_UNESCAPED_SLASHES = 64;
+    const JSON_PRETTY_PRINT = 128;
+    const JSON_UNESCAPED_UNICODE = 256;
+
     private $path;
 
     /**
@@ -108,7 +102,7 @@ class JsonFile
                 );
             }
         }
-        file_put_contents($this->path, static::encode($hash, $options). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
+        file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : ''));
     }
 
     /**
@@ -170,9 +164,9 @@ class JsonFile
 
         $json = json_encode($data);
 
-        $prettyPrint = (Boolean) ($options & JSON_PRETTY_PRINT);
-        $unescapeUnicode = (Boolean) ($options & JSON_UNESCAPED_UNICODE);
-        $unescapeSlashes = (Boolean) ($options & JSON_UNESCAPED_SLASHES);
+        $prettyPrint = (Boolean) ($options & self::JSON_PRETTY_PRINT);
+        $unescapeUnicode = (Boolean) ($options & self::JSON_UNESCAPED_UNICODE);
+        $unescapeSlashes = (Boolean) ($options & self::JSON_UNESCAPED_SLASHES);
 
         if (!$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) {
             return $json;

+ 6 - 4
src/Composer/Repository/PlatformRepository.php

@@ -36,14 +36,15 @@ class PlatformRepository extends ArrayRepository
         }
 
         $php = new MemoryPackage('php', $version, $prettyVersion);
+        $php->setDescription('The PHP interpreter');
         parent::addPackage($php);
 
-        foreach (get_loaded_extensions() as $ext) {
-            if (in_array($ext, array('standard', 'Core'))) {
+        foreach (get_loaded_extensions() as $name) {
+            if (in_array($name, array('standard', 'Core'))) {
                 continue;
             }
 
-            $reflExt = new \ReflectionExtension($ext);
+            $reflExt = new \ReflectionExtension($name);
             try {
                 $prettyVersion = $reflExt->getVersion();
                 $version = $versionParser->normalize($prettyVersion);
@@ -52,7 +53,8 @@ class PlatformRepository extends ArrayRepository
                 $version = $versionParser->normalize($prettyVersion);
             }
 
-            $ext = new MemoryPackage('ext-'.strtolower($ext), $version, $prettyVersion);
+            $ext = new MemoryPackage('ext-'.$name, $version, $prettyVersion);
+            $ext->setDescription('The '.$name.' PHP extension');
             parent::addPackage($ext);
         }
     }

+ 5 - 19
src/Composer/Repository/Vcs/GitBitbucketDriver.php

@@ -49,7 +49,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository), true);
+            $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository));
             $this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master';
         }
 
@@ -93,13 +93,13 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
         if (!isset($this->infoCache[$identifier])) {
             $composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
             if (!$composer) {
-                throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
+                return;
             }
 
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
+                $changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier));
                 $composer['time'] = $changeset['timestamp'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -114,7 +114,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
             $this->tags = array();
             foreach ($tagsData as $tag => $data) {
                 $this->tags[$tag] = $data['raw_node'];
@@ -130,7 +130,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'));
             $this->branches = array();
             foreach ($branchData as $branch => $data) {
                 $this->branches[$branch] = $data['raw_node'];
@@ -140,20 +140,6 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
         return $this->branches;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 2 - 16
src/Composer/Repository/Vcs/GitDriver.php

@@ -9,7 +9,7 @@ use Composer\IO\IOInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GitDriver extends VcsDriver implements VcsDriverInterface
+class GitDriver extends VcsDriver
 {
     protected $tags;
     protected $branches;
@@ -117,7 +117,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
             $this->process->execute(sprintf('cd %s && git show %s:composer.json', escapeshellarg($this->repoDir), escapeshellarg($identifier)), $composer);
 
             if (!trim($composer)) {
-                throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
+                return;
             }
 
             $composer = JsonFile::parseJson($composer);
@@ -173,20 +173,6 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
         return $this->branches;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 6 - 20
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -8,7 +8,7 @@ use Composer\IO\IOInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GitHubDriver extends VcsDriver implements VcsDriverInterface
+class GitHubDriver extends VcsDriver
 {
     protected $owner;
     protected $repository;
@@ -39,7 +39,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository), true);
+            $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository));
             $this->rootIdentifier = $repoData['master_branch'] ?: 'master';
         }
 
@@ -83,13 +83,13 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
         if (!isset($this->infoCache[$identifier])) {
             $composer = $this->getContents($this->getScheme() . '://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json');
             if (!$composer) {
-                throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
+                return;
             }
 
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $commit = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier), true);
+                $commit = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier));
                 $composer['time'] = $commit['commit']['committer']['date'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -104,7 +104,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'));
             $this->tags = array();
             foreach ($tagsData as $tag) {
                 $this->tags[$tag['name']] = $tag['commit']['sha'];
@@ -120,7 +120,7 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'));
             $this->branches = array();
             foreach ($branchData as $branch) {
                 $this->branches[$branch['name']] = $branch['commit']['sha'];
@@ -130,20 +130,6 @@ class GitHubDriver extends VcsDriver implements VcsDriverInterface
         return $this->branches;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 7 - 21
src/Composer/Repository/Vcs/HgBitbucketDriver.php

@@ -18,7 +18,7 @@ use Composer\IO\IOInterface;
 /**
  * @author Per Bernhardt <plb@webfactory.de>
  */
-class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
+class HgBitbucketDriver extends VcsDriver
 {
     protected $owner;
     protected $repository;
@@ -49,7 +49,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
             $this->rootIdentifier = $repoData['tip']['raw_node'];
         }
 
@@ -93,13 +93,13 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
         if (!isset($this->infoCache[$identifier])) {
             $composer = $this->getContents($this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
             if (!$composer) {
-                throw new \UnexpectedValueException('Failed to retrieve composer information for identifier '.$identifier.' in '.$this->getUrl());
+                return;
             }
 
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
+                $changeset = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier));
                 $composer['time'] = $changeset['timestamp'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -114,7 +114,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'));
             $this->tags = array();
             foreach ($tagsData as $tag => $data) {
                 $this->tags[$tag] = $data['raw_node'];
@@ -130,7 +130,7 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'));
             $this->branches = array();
             foreach ($branchData as $branch => $data) {
                 $this->branches[$branch] = $data['raw_node'];
@@ -140,25 +140,11 @@ class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
         return $this->branches;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */
     public static function supports($url, $deep = false)
     {
-        return preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url);
+        return extension_loaded('openssl') && preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url);
     }
 }

+ 2 - 16
src/Composer/Repository/Vcs/HgDriver.php

@@ -19,7 +19,7 @@ use Composer\IO\IOInterface;
 /**
  * @author Per Bernhardt <plb@webfactory.de>
  */
-class HgDriver extends VcsDriver implements VcsDriverInterface
+class HgDriver extends VcsDriver
 {
     protected $tags;
     protected $branches;
@@ -100,7 +100,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
             $this->process->execute(sprintf('cd %s && hg cat -r %s composer.json', escapeshellarg($this->tmpDir), escapeshellarg($identifier)), $composer);
 
             if (!trim($composer)) {
-                throw new \UnexpectedValueException('Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl());
+                return;
             }
 
             $composer = JsonFile::parseJson($composer);
@@ -159,20 +159,6 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
         return $this->branches;
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 3 - 20
src/Composer/Repository/Vcs/SvnDriver.php

@@ -9,7 +9,7 @@ use Composer\IO\IOInterface;
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class SvnDriver extends VcsDriver implements VcsDriverInterface
+class SvnDriver extends VcsDriver
 {
     protected $baseUrl;
     protected $tags;
@@ -158,7 +158,7 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface
     {
         $identifier = '/' . trim($identifier, '/') . '/';
         if (!isset($this->infoCache[$identifier])) {
-            preg_match('{^(.+?)(@\d+)?$}', $identifier, $match);
+            preg_match('{^(.+?)(@\d+)?/$}', $identifier, $match);
             if (!empty($match[2])) {
                 $identifier = $match[1];
                 $rev = $match[2];
@@ -167,11 +167,8 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface
             }
 
             $output = $this->execute('svn cat', $this->baseUrl . $identifier . 'composer.json' . $rev);
-
             if (!trim($output)) {
-                throw new \UnexpectedValueException(
-                    'Failed to retrieve composer information for identifier ' . $identifier . ' in ' . $this->getUrl()
-                );
+                return;
             }
 
             $composer = JsonFile::parseJson($output);
@@ -312,20 +309,6 @@ class SvnDriver extends VcsDriver implements VcsDriverInterface
         return '--non-interactive ';
     }
 
-    /**
-     * {@inheritDoc}
-     */
-    public function hasComposerFile($identifier)
-    {
-        try {
-            $this->getComposerInformation($identifier);
-            return true;
-        } catch (\Exception $e) {
-        }
-
-        return false;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 16 - 1
src/Composer/Repository/Vcs/VcsDriver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Repository\Vcs;
 
+use Composer\Downloader\TransportException;
 use Composer\IO\IOInterface;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\RemoteFilesystem;
@@ -21,7 +22,7 @@ use Composer\Util\RemoteFilesystem;
  *
  * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
-abstract class VcsDriver
+abstract class VcsDriver implements VcsDriverInterface
 {
     protected $url;
     protected $io;
@@ -41,6 +42,20 @@ abstract class VcsDriver
         $this->process = $process ?: new ProcessExecutor;
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public function hasComposerFile($identifier)
+    {
+        try {
+            return (Boolean) $this->getComposerInformation($identifier);
+        } catch (TransportException $e) {
+        }
+
+        return false;
+    }
+
+
     /**
      * Get the https or http protocol depending on SSL support.
      *

+ 91 - 58
src/Composer/Repository/VcsRepository.php

@@ -2,6 +2,7 @@
 
 namespace Composer\Repository;
 
+use Composer\Downloader\TransportException;
 use Composer\Repository\Vcs\VcsDriverInterface;
 use Composer\Package\Version\VersionParser;
 use Composer\Package\PackageInterface;
@@ -19,20 +20,22 @@ class VcsRepository extends ArrayRepository
     protected $debug;
     protected $io;
     protected $versionParser;
+    protected $type;
 
     public function __construct(array $config, IOInterface $io, array $drivers = null)
     {
         $this->drivers = $drivers ?: array(
-            'Composer\Repository\Vcs\GitHubDriver',
-            'Composer\Repository\Vcs\GitBitbucketDriver',
-            'Composer\Repository\Vcs\GitDriver',
-            'Composer\Repository\Vcs\SvnDriver',
-            'Composer\Repository\Vcs\HgBitbucketDriver',
-            'Composer\Repository\Vcs\HgDriver',
+            'github'        => 'Composer\Repository\Vcs\GitHubDriver',
+            'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
+            'git'           => 'Composer\Repository\Vcs\GitDriver',
+            'svn'           => 'Composer\Repository\Vcs\SvnDriver',
+            'hg-bitbucket'  => 'Composer\Repository\Vcs\HgBitbucketDriver',
+            'hg'            => 'Composer\Repository\Vcs\HgDriver',
         );
 
         $this->url = $config['url'];
         $this->io = $io;
+        $this->type = $config['type'];
     }
 
     public function setDebug($debug)
@@ -42,6 +45,13 @@ class VcsRepository extends ArrayRepository
 
     public function getDriver()
     {
+        if (isset($this->drivers[$this->type])) {
+            $class = $this->drivers[$this->type];
+            $driver = new $class($this->url, $this->io);
+            $driver->initialize();
+            return $driver;
+        }
+
         foreach ($this->drivers as $driver) {
             if ($driver::supports($this->url)) {
                 $driver = new $driver($this->url, $this->io);
@@ -73,9 +83,15 @@ class VcsRepository extends ArrayRepository
         $this->versionParser = new VersionParser;
         $loader = new ArrayLoader();
 
-        if ($driver->hasComposerFile($driver->getRootIdentifier())) {
-            $data = $driver->getComposerInformation($driver->getRootIdentifier());
-            $this->packageName = !empty($data['name']) ? $data['name'] : null;
+        try {
+            if ($driver->hasComposerFile($driver->getRootIdentifier())) {
+                $data = $driver->getComposerInformation($driver->getRootIdentifier());
+                $this->packageName = !empty($data['name']) ? $data['name'] : null;
+            }
+        } catch (\Exception $e) {
+            if ($debug) {
+                $this->io->write('Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage());
+            }
         }
 
         foreach ($driver->getTags() as $tag => $identifier) {
@@ -86,46 +102,53 @@ class VcsRepository extends ArrayRepository
                 $this->io->overwrite($msg, false);
             }
 
-            $parsedTag = $this->validateTag($tag);
-            if ($parsedTag && $driver->hasComposerFile($identifier)) {
-                try {
-                    $data = $driver->getComposerInformation($identifier);
-                } catch (\Exception $e) {
+            if (!$parsedTag = $this->validateTag($tag)) {
+                if ($debug) {
+                    $this->io->write('Skipped tag '.$tag.', invalid tag name');
+                }
+                continue;
+            }
+
+            try {
+                if (!$data = $driver->getComposerInformation($identifier)) {
                     if ($debug) {
-                        $this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
+                        $this->io->write('Skipped tag '.$tag.', no composer file');
                     }
                     continue;
                 }
-
-                // manually versioned package
-                if (isset($data['version'])) {
-                    $data['version_normalized'] = $this->versionParser->normalize($data['version']);
-                } else {
-                    // auto-versionned package, read value from tag
-                    $data['version'] = $tag;
-                    $data['version_normalized'] = $parsedTag;
+            } catch (\Exception $e) {
+                if ($debug) {
+                    $this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
                 }
+                continue;
+            }
 
-                // make sure tag packages have no -dev flag
-                $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
-                $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
+            // manually versioned package
+            if (isset($data['version'])) {
+                $data['version_normalized'] = $this->versionParser->normalize($data['version']);
+            } else {
+                // auto-versionned package, read value from tag
+                $data['version'] = $tag;
+                $data['version_normalized'] = $parsedTag;
+            }
 
-                // broken package, version doesn't match tag
-                if ($data['version_normalized'] !== $parsedTag) {
-                    if ($debug) {
-                        $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json');
-                    }
-                    continue;
-                }
+            // make sure tag packages have no -dev flag
+            $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
+            $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
 
+            // broken package, version doesn't match tag
+            if ($data['version_normalized'] !== $parsedTag) {
                 if ($debug) {
-                    $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')');
+                    $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json');
                 }
+                continue;
+            }
 
-                $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
-            } elseif ($debug) {
-                $this->io->write('Skipped tag '.$tag.', '.($parsedTag ? 'no composer file was found' : 'invalid name'));
+            if ($debug) {
+                $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')');
             }
+
+            $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
         }
 
         $this->io->overwrite('', false);
@@ -138,36 +161,46 @@ class VcsRepository extends ArrayRepository
                 $this->io->overwrite($msg, false);
             }
 
-            $parsedBranch = $this->validateBranch($branch);
-            if ($driver->hasComposerFile($identifier)) {
-                $data = $driver->getComposerInformation($identifier);
+            if (!$parsedBranch = $this->validateBranch($branch)) {
+                if ($debug) {
+                    $this->io->write('Skipped branch '.$branch.', invalid name');
+                }
+                continue;
+            }
 
-                if (!$parsedBranch) {
+            try {
+                if (!$data = $driver->getComposerInformation($identifier)) {
                     if ($debug) {
-                        $this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found');
+                        $this->io->write('Skipped branch '.$branch.', no composer file');
                     }
                     continue;
                 }
-
-                // branches are always auto-versionned, read value from branch name
-                $data['version'] = $branch;
-                $data['version_normalized'] = $parsedBranch;
-
-                // make sure branch packages have a dev flag
-                if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
-                    $data['version'] = 'dev-' . $data['version'];
-                } else {
-                    $data['version'] = $data['version'] . '-dev';
-                }
-
+            } catch (TransportException $e) {
                 if ($debug) {
-                    $this->io->write('Importing branch '.$branch.' ('.$data['version_normalized'].')');
+                    $this->io->write('Skipped branch '.$branch.', no composer file was found');
                 }
+                continue;
+            } catch (\Exception $e) {
+                $this->io->write('Skipped branch '.$branch.', '.$e->getMessage());
+                continue;
+            }
 
-                $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
-            } elseif ($debug) {
-                $this->io->write('Skipped branch '.$branch.', no composer file was found');
+            // branches are always auto-versionned, read value from branch name
+            $data['version'] = $branch;
+            $data['version_normalized'] = $parsedBranch;
+
+            // make sure branch packages have a dev flag
+            if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
+                $data['version'] = 'dev-' . $data['version'];
+            } else {
+                $data['version'] = $data['version'] . '-dev';
             }
+
+            if ($debug) {
+                $this->io->write('Importing branch '.$branch.' ('.$data['version_normalized'].')');
+            }
+
+            $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
         }
 
         $this->io->overwrite('', false);

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

@@ -66,11 +66,12 @@ class Filesystem
             throw new \InvalidArgumentException('from and to must be absolute paths');
         }
 
+        $from = lcfirst(rtrim(strtr($from, '\\', '/'), '/'));
+        $to = lcfirst(rtrim(strtr($to, '\\', '/'), '/'));
+
         if (dirname($from) === dirname($to)) {
             return './'.basename($to);
         }
-        $from = lcfirst(rtrim(strtr($from, '\\', '/'), '/'));
-        $to = lcfirst(rtrim(strtr($to, '\\', '/'), '/'));
 
         $commonPath = $to;
         while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
@@ -101,11 +102,12 @@ class Filesystem
             throw new \InvalidArgumentException('from and to must be absolute paths');
         }
 
+        $from = lcfirst(strtr($from, '\\', '/'));
+        $to = lcfirst(strtr($to, '\\', '/'));
+
         if ($from === $to) {
             return $directories ? '__DIR__' : '__FILE__';
         }
-        $from = lcfirst(strtr($from, '\\', '/'));
-        $to = lcfirst(strtr($to, '\\', '/'));
 
         $commonPath = $to;
         while (strpos($from, $commonPath) !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {

+ 12 - 6
src/Composer/Util/RemoteFilesystem.php

@@ -13,6 +13,7 @@
 namespace Composer\Util;
 
 use Composer\IO\IOInterface;
+use Composer\Downloader\TransportException;
 
 /**
  * @author François Pluchino <francois.pluchino@opendisplay.com>
@@ -81,7 +82,7 @@ class RemoteFilesystem
      * @param boolean $progress  Display the progression
      * @param boolean $firstCall Whether this is the first attempt at fetching this resource
      *
-     * @throws \RuntimeException When the file could not be downloaded
+     * @throws TransportException When the file could not be downloaded
      */
     protected function get($originUrl, $fileUrl, $fileName = null, $progress = true, $firstCall = true)
     {
@@ -98,7 +99,7 @@ class RemoteFilesystem
         $ctx = StreamContextFactory::getContext($options, array('notification' => array($this, 'callbackGet')));
 
         if ($this->progress) {
-            $this->io->overwrite("    Downloading: <comment>connection...</comment>", false);
+            $this->io->write("    Downloading: <comment>connection...</comment>", false);
         }
 
         if (null !== $fileName) {
@@ -107,6 +108,11 @@ class RemoteFilesystem
             $result = @file_get_contents($fileUrl, false, $ctx);
         }
 
+        // fix for 5.4.0 https://bugs.php.net/bug.php?id=61336
+        if (!empty($http_response_header[0]) && preg_match('{^HTTP/\S+ 404}i', $http_response_header[0])) {
+            $result = false;
+        }
+
         // avoid overriding if content was loaded by a sub-call to get()
         if (null === $this->result) {
             $this->result = $result;
@@ -117,7 +123,7 @@ class RemoteFilesystem
         }
 
         if (false === $this->result) {
-            throw new \RuntimeException("The '$fileUrl' file could not be downloaded");
+            throw new TransportException("The '$fileUrl' file could not be downloaded");
         }
     }
 
@@ -137,7 +143,7 @@ class RemoteFilesystem
             case STREAM_NOTIFY_AUTH_REQUIRED:
             case STREAM_NOTIFY_FAILURE:
                 if (404 === $messageCode && !$this->firstCall) {
-                    throw new \RuntimeException("The '" . $this->fileUrl . "' URL not found");
+                    throw new TransportException("The '" . $this->fileUrl . "' URL not found", 404);
                 }
 
                 // for private repository returning 404 error when the authorization is incorrect
@@ -149,9 +155,9 @@ class RemoteFilesystem
                 // get authorization informations
                 if (401 === $messageCode || $attemptAuthentication) {
                     if (!$this->io->isInteractive()) {
-                        $mess = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console";
+                        $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console";
 
-                        throw new \RuntimeException($mess);
+                        throw new TransportException($message, 401);
                     }
 
                     $this->io->overwrite('    Authentication required (<info>'.parse_url($this->fileUrl, PHP_URL_HOST).'</info>):');

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

@@ -134,6 +134,41 @@ class AutoloadGeneratorTest extends TestCase
         mkdir($this->vendorDir.'/.composer', 0777, true);
         $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
         $this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer');
+        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
+    }
+
+    public function testVendorsClassMapAutoloading()
+    {
+        $package = new MemoryPackage('a', '1.0', '1.0');
+
+        $packages = array();
+        $packages[] = $a = new MemoryPackage('a/a', '1.0', '1.0');
+        $packages[] = $b = new MemoryPackage('b/b', '1.0', '1.0');
+        $a->setAutoload(array('classmap' => array('src/')));
+        $b->setAutoload(array('classmap' => array('src/', 'lib/')));
+
+        $this->repository->expects($this->once())
+            ->method('getPackages')
+            ->will($this->returnValue($packages));
+
+        @mkdir($this->vendorDir.'/.composer', 0777, true);
+        mkdir($this->vendorDir.'/a/a/src', 0777, true);
+        mkdir($this->vendorDir.'/b/b/src', 0777, true);
+        mkdir($this->vendorDir.'/b/b/lib', 0777, true);
+        file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
+        file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
+        file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
+
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
+        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
+        $this->assertEquals(
+            array(
+                'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
+                'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.php',
+                'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/b/b/lib/c.php',
+            ),
+            include ($this->vendorDir.'/.composer/autoload_classmap.php')
+        );
     }
 
     public function testOverrideVendorsAutoloading()

+ 83 - 0
tests/Composer/Test/Autoload/ClassMapGeneratorTest.php

@@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file was copied from the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Autoload;
+
+use Composer\Autoload\ClassMapGenerator;
+
+class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider getTestCreateMapTests
+     */
+    public function testCreateMap($directory, $expected)
+    {
+        $this->assertEqualsNormalized($expected, ClassMapGenerator::createMap($directory));
+    }
+
+    public function getTestCreateMapTests()
+    {
+        return array(
+            array(__DIR__.'/Fixtures/Namespaced', array(
+                'Namespaced\\Bar' => realpath(__DIR__).'/Fixtures/Namespaced/Bar.php',
+                'Namespaced\\Foo' => realpath(__DIR__).'/Fixtures/Namespaced/Foo.php',
+                'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php',
+                )
+            ),
+            array(__DIR__.'/Fixtures/beta/NamespaceCollision', array(
+                'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+                'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+            )),
+            array(__DIR__.'/Fixtures/Pearlike', array(
+                'Pearlike_Foo' => realpath(__DIR__).'/Fixtures/Pearlike/Foo.php',
+                'Pearlike_Bar' => realpath(__DIR__).'/Fixtures/Pearlike/Bar.php',
+                'Pearlike_Baz' => realpath(__DIR__).'/Fixtures/Pearlike/Baz.php',
+            )),
+            array(__DIR__.'/Fixtures/classmap', array(
+                'Foo\\Bar\\A'             => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+                'Foo\\Bar\\B'             => realpath(__DIR__).'/Fixtures/classmap/sameNsMultipleClasses.php',
+                'Alpha\\A'                => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+                'Alpha\\B'                => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+                'Beta\\A'                 => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+                'Beta\\B'                 => realpath(__DIR__).'/Fixtures/classmap/multipleNs.php',
+                'ClassMap\\SomeInterface' => realpath(__DIR__).'/Fixtures/classmap/SomeInterface.php',
+                'ClassMap\\SomeParent'    => realpath(__DIR__).'/Fixtures/classmap/SomeParent.php',
+                'ClassMap\\SomeClass'     => realpath(__DIR__).'/Fixtures/classmap/SomeClass.php',
+            )),
+        );
+    }
+
+    public function testCreateMapFinderSupport()
+    {
+        if (!class_exists('Symfony\\Component\\Finder\\Finder')) {
+            $this->markTestSkipped('Finder component is not available');
+        }
+
+        $finder = new \Symfony\Component\Finder\Finder();
+        $finder->files()->in(__DIR__ . '/Fixtures/beta/NamespaceCollision');
+
+        $this->assertEqualsNormalized(array(
+            'NamespaceCollision\\A\\B\\Bar' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Bar.php',
+            'NamespaceCollision\\A\\B\\Foo' => realpath(__DIR__).'/Fixtures/beta/NamespaceCollision/A/B/Foo.php',
+        ), ClassMapGenerator::createMap($finder));
+    }
+
+    protected function assertEqualsNormalized($expected, $actual, $message = null)
+    {
+        foreach ($expected as $ns => $path) {
+            $expected[$ns] = strtr($path, '\\', '/');
+        }
+        foreach ($actual as $ns => $path) {
+            $actual[$ns] = strtr($path, '\\', '/');
+        }
+        $this->assertEquals($expected, $actual, $message);
+    }
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/Namespaced/Bar.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Namespaced;
+
+class Bar
+{
+    public static $loaded = true;
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/Namespaced/Baz.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Namespaced;
+
+class Baz
+{
+    public static $loaded = true;
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/Namespaced/Foo.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Namespaced;
+
+class Foo
+{
+    public static $loaded = true;
+}

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/Pearlike/Bar.php

@@ -0,0 +1,6 @@
+<?php
+
+class Pearlike_Bar
+{
+    public static $loaded = true;
+}

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/Pearlike/Baz.php

@@ -0,0 +1,6 @@
+<?php
+
+class Pearlike_Baz
+{
+    public static $loaded = true;
+}

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/Pearlike/Foo.php

@@ -0,0 +1,6 @@
+<?php
+
+class Pearlike_Foo
+{
+    public static $loaded = true;
+}

+ 3 - 2
tests/Composer/Test/Autoload/Fixtures/autoload_main.php

@@ -3,8 +3,9 @@
 // autoload_namespace.php generated by Composer
 
 $vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
 
 return array(
-    'Main' => dirname($vendorDir) . '/src/',
-    'Lala' => dirname($vendorDir) . '/src/',
+    'Main' => $baseDir . '/src/',
+    'Lala' => $baseDir . '/src/',
 );

+ 3 - 2
tests/Composer/Test/Autoload/Fixtures/autoload_main2.php

@@ -3,8 +3,9 @@
 // autoload_namespace.php generated by Composer
 
 $vendorDir = dirname(__DIR__);
+$baseDir = dirname(dirname($vendorDir));
 
 return array(
-    'Main' => dirname(dirname($vendorDir)) . '/src/',
-    'Lala' => dirname(dirname($vendorDir)) . '/src/',
+    'Main' => $baseDir . '/src/',
+    'Lala' => $baseDir . '/src/',
 );

+ 3 - 2
tests/Composer/Test/Autoload/Fixtures/autoload_main3.php

@@ -3,8 +3,9 @@
 // autoload_namespace.php generated by Composer
 
 $vendorDir = dirname(__DIR__);
+$baseDir = $vendorDir;
 
 return array(
-    'Main' => $vendorDir . '/src/',
-    'Lala' => $vendorDir . '/src/',
+    'Main' => $baseDir . '/src/',
+    'Lala' => $baseDir . '/src/',
 );

+ 1 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_override_vendors.php

@@ -3,6 +3,7 @@
 // autoload_namespace.php generated by Composer
 
 $vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
 
 return array(
     'B\\Sub\\Name' => $vendorDir . '/b/b/src/',

+ 1 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_vendors.php

@@ -3,6 +3,7 @@
 // autoload_namespace.php generated by Composer
 
 $vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
 
 return array(
     'B\\Sub\\Name' => $vendorDir . '/b/b/src/',

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Bar.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace NamespaceCollision\A\B;
+
+class Bar
+{
+    public static $loaded = true;
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/beta/NamespaceCollision/A/B/Foo.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace NamespaceCollision\A\B;
+
+class Foo
+{
+    public static $loaded = true;
+}

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Bar.php

@@ -0,0 +1,6 @@
+<?php
+
+class PrefixCollision_A_B_Bar
+{
+    public static $loaded = true;
+}

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/beta/PrefixCollision/A/B/Foo.php

@@ -0,0 +1,6 @@
+<?php
+
+class PrefixCollision_A_B_Foo
+{
+    public static $loaded = true;
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/SomeClass.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ClassMap;
+
+class SomeClass extends SomeParent implements SomeInterface
+{
+
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/SomeInterface.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ClassMap;
+
+interface SomeInterface
+{
+
+}

+ 8 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/SomeParent.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace ClassMap;
+
+abstract class SomeParent
+{
+
+}

+ 11 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/multipleNs.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Alpha {
+    class A {}
+    class B {}
+}
+
+namespace Beta {
+    class A {}
+    class B {}
+}

+ 3 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/notAClass.php

@@ -0,0 +1,3 @@
+<?php
+
+$a = new stdClass();

+ 1 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/notPhpFile.md

@@ -0,0 +1 @@
+This file should be skipped.

+ 6 - 0
tests/Composer/Test/Autoload/Fixtures/classmap/sameNsMultipleClasses.php

@@ -0,0 +1,6 @@
+<?php
+
+namespace Foo\Bar;
+
+class A {}
+class B {}

+ 62 - 0
tests/Composer/Test/ComposerTest.php

@@ -0,0 +1,62 @@
+<?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\Test;
+
+use Composer\Composer;
+
+class ComposerTest extends TestCase
+{
+    public function testSetGetPackage()
+    {
+        $composer = new Composer();
+        $package = $this->getMock('Composer\Package\PackageInterface');
+        $composer->setPackage($package);
+
+        $this->assertSame($package, $composer->getPackage());
+    }
+
+    public function testSetGetLocker()
+    {
+        $composer = new Composer();
+        $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
+        $composer->setLocker($locker);
+
+        $this->assertSame($locker, $composer->getLocker());
+    }
+
+    public function testSetGetRepositoryManager()
+    {
+        $composer = new Composer();
+        $manager = $this->getMockBuilder('Composer\Repository\RepositoryManager')->disableOriginalConstructor()->getMock();
+        $composer->setRepositoryManager($manager);
+
+        $this->assertSame($manager, $composer->getRepositoryManager());
+    }
+
+    public function testSetGetDownloadManager()
+    {
+        $composer = new Composer();
+        $manager = $this->getMock('Composer\Downloader\DownloadManager');
+        $composer->setDownloadManager($manager);
+
+        $this->assertSame($manager, $composer->getDownloadManager());
+    }
+
+    public function testSetGetInstallationManager()
+    {
+        $composer = new Composer();
+        $manager = $this->getMock('Composer\Installer\InstallationManager');
+        $composer->setInstallationManager($manager);
+
+        $this->assertSame($manager, $composer->getInstallationManager());
+    }
+}

+ 16 - 0
tests/Composer/Test/Downloader/ArchiveDownloaderTest.php

@@ -32,4 +32,20 @@ class ArchiveDownloaderTest extends \PHPUnit_Framework_TestCase
         $this->assertRegExp('#/path/[a-z0-9]+\.js#', $first);
         $this->assertSame($first, $method->invoke($downloader, $packageMock, '/path'));
     }
+
+    public function testProcessUrl()
+    {
+        $downloader = $this->getMockForAbstractClass('Composer\Downloader\ArchiveDownloader', array($this->getMock('Composer\IO\IOInterface')));
+        $method = new \ReflectionMethod($downloader, 'processUrl');
+        $method->setAccessible(true);
+
+        $expected = 'https://github.com/composer/composer/zipball/master';
+        $url = $method->invoke($downloader, $expected);
+
+        if (extension_loaded('openssl')) {
+            $this->assertEquals($expected, $url);
+        } else {
+            $this->assertEquals('http://nodeload.github.com/composer/composer/zipball/master', $url);
+        }
+    }
 }

+ 12 - 12
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -41,7 +41,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
     public function testDownload()
     {
-        $expectedGitCommand = $this->getCmd('git clone \'https://example.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
             ->method('getSourceReference')
@@ -70,19 +70,19 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('https://github.com/composer/composer'));
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
 
-        $expectedGitCommand = $this->getCmd('git clone \'git://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
         $processExecutor->expects($this->at(0))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(1));
 
-        $expectedGitCommand = $this->getCmd('git clone \'https://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(1));
 
-        $expectedGitCommand = $this->getCmd('git clone \'http://github.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
         $processExecutor->expects($this->at(2))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -97,7 +97,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
      */
     public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
     {
-        $expectedGitCommand = $this->getCmd('git clone \'https://example.com/composer/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
             ->method('getSourceReference')
@@ -132,8 +132,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
     public function testUpdate()
     {
-        $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\'');
-        $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain');
+        $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
@@ -141,7 +141,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('ref'));
         $packageMock->expects($this->any())
             ->method('getSourceUrl')
-            ->will($this->returnValue('https://github.com/l3l0/composer'));
+            ->will($this->returnValue('https://github.com/composer/composer'));
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->at(0))
             ->method('execute')
@@ -161,8 +161,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
      */
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     {
-        $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\'');
-        $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain');
+        $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
@@ -170,7 +170,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('ref'));
         $packageMock->expects($this->any())
             ->method('getSourceUrl')
-            ->will($this->returnValue('https://github.com/l3l0/composer'));
+            ->will($this->returnValue('https://github.com/composer/composer'));
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->at(0))
             ->method('execute')
@@ -187,7 +187,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
     public function testRemove()
     {
-        $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain');
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');

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

@@ -75,8 +75,8 @@ class HgDownloaderTest extends \PHPUnit_Framework_TestCase
 
     public function testUpdate()
     {
-        $expectedUpdateCommand = $this->getCmd('cd \'composerPath\' && hg pull && hg up \'ref\'');
-        $expectedResetCommand = $this->getCmd('cd \'composerPath\' && hg st');
+        $expectedUpdateCommand = $this->getCmd("cd 'composerPath' && hg pull 'https://github.com/l3l0/composer' && hg up 'ref'");
+        $expectedResetCommand = $this->getCmd("cd 'composerPath' && hg st");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())

+ 17 - 17
tests/Composer/Test/IO/ConsoleIOTest.php

@@ -53,35 +53,35 @@ class ConsoleIOTest extends TestCase
     {
         $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
         $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
+
         $outputMock->expects($this->at(0))
             ->method('write')
-            ->with($this->equalTo("\x08"), $this->equalTo(false));
-        $outputMock->expects($this->at(19))
-            ->method('write')
-            ->with($this->equalTo("\x08"), $this->equalTo(false));
-        $outputMock->expects($this->at(20))
+            ->with($this->equalTo('something (<question>strlen = 23</question>)'));
+        $outputMock->expects($this->at(1))
             ->method('write')
-            ->with($this->equalTo('some information'), $this->equalTo(false));
-        $outputMock->expects($this->at(21))
+            ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
+        $outputMock->expects($this->at(2))
             ->method('write')
-            ->with($this->equalTo(' '), $this->equalTo(false));
-        $outputMock->expects($this->at(24))
+            ->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
+        $outputMock->expects($this->at(3))
             ->method('write')
-            ->with($this->equalTo(' '), $this->equalTo(false));
-        $outputMock->expects($this->at(25))
+            ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
+        $outputMock->expects($this->at(4))
             ->method('write')
-            ->with($this->equalTo("\x08"), $this->equalTo(false));
-        $outputMock->expects($this->at(28))
+            ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
+        $outputMock->expects($this->at(5))
             ->method('write')
-            ->with($this->equalTo("\x08"), $this->equalTo(false));
-        $outputMock->expects($this->at(29))
+            ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
+        $outputMock->expects($this->at(6))
             ->method('write')
-            ->with($this->equalTo(''));
+            ->with($this->equalTo('something longer than initial (<info>34</info>)'));
 
         $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet');
 
         $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock);
-        $consoleIO->overwrite('some information', true, 20);
+        $consoleIO->write('something (<question>strlen = 23</question>)');
+        $consoleIO->overwrite('shorter (<comment>12</comment>)', false);
+        $consoleIO->overwrite('something longer than initial (<info>34</info>)');
     }
 
     public function testAsk()

+ 84 - 0
tests/Composer/Test/IO/NullIOTest.php

@@ -0,0 +1,84 @@
+<?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\Test\IO;
+
+use Composer\IO\NullIO;
+use Composer\Test\TestCase;
+
+class NullIOTest extends TestCase
+{
+    public function testIsInteractive()
+    {
+        $io = new NullIO();
+
+        $this->assertFalse($io->isInteractive());
+    }
+
+    public function testHasAuthorization()
+    {
+        $io = new NullIO();
+
+        $this->assertFalse($io->hasAuthorization('foo'));
+    }
+
+    public function testGetLastPassword()
+    {
+        $io = new NullIO();
+
+        $this->assertNull($io->getLastPassword());
+    }
+
+    public function testGetLastUsername()
+    {
+        $io = new NullIO();
+
+        $this->assertNull($io->getLastUsername());
+    }
+
+    public function testAskAndHideAnswer()
+    {
+        $io = new NullIO();
+
+        $this->assertNull($io->askAndHideAnswer('foo'));
+    }
+
+    public function testGetAuthorizations()
+    {
+        $io = new NullIO();
+
+        $this->assertInternalType('array', $io->getAuthorizations());
+        $this->assertEmpty($io->getAuthorizations());
+        $this->assertEquals(array('username' => null, 'password' => null), $io->getAuthorization('foo'));
+    }
+
+    public function testAsk()
+    {
+        $io = new NullIO();
+
+        $this->assertEquals('foo', $io->ask('bar', 'foo'));
+    }
+
+    public function testAskConfirmation()
+    {
+        $io = new NullIO();
+
+        $this->assertEquals('foo', $io->askConfirmation('bar', 'foo'));
+    }
+
+    public function testAskAndValidate()
+    {
+        $io = new NullIO();
+
+        $this->assertEquals('foo', $io->askAndValidate('question', 'validator', false, 'foo'));
+    }
+}

+ 15 - 5
tests/Composer/Test/Json/JsonFileTest.php

@@ -140,9 +140,10 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
 
     public function testUnicode()
     {
-        if (!function_exists('mb_convert_encoding')) {
+        if (!function_exists('mb_convert_encoding') && version_compare(PHP_VERSION, '5.4', '<')) {
             $this->markTestSkipped('Test requires the mbstring extension');
         }
+
         $data = array("Žluťoučký \" kůň" => "úpěl ďábelské ódy za €");
         $json = '{
     "Žluťoučký \" kůň": "úpěl ďábelské ódy za €"
@@ -151,14 +152,23 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
         $this->assertJsonFormat($json, $data);
     }
 
-    public function testEscapedSlashes()
+    public function testOnlyUnicode()
     {
-        if (!function_exists('mb_convert_encoding')) {
+        if (!function_exists('mb_convert_encoding') && version_compare(PHP_VERSION, '5.4', '<')) {
             $this->markTestSkipped('Test requires the mbstring extension');
         }
-        $data = "\\/fooƌ";
 
-        $this->assertJsonFormat('"\\\\\\/fooƌ"', $data, JSON_UNESCAPED_UNICODE);
+        $data = "\\/ƌ";
+
+        $this->assertJsonFormat('"\\\\\\/ƌ"', $data, JsonFile::JSON_UNESCAPED_UNICODE);
+    }
+
+    public function testEscapedSlashes()
+    {
+
+        $data = "\\/foo";
+
+        $this->assertJsonFormat('"\\\\\\/foo"', $data, 0);
     }
 
     public function testEscapedUnicode()

+ 13 - 13
tests/Composer/Test/Repository/Vcs/SvnDriverTest.php

@@ -27,23 +27,14 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
      *
      * @return array
      */
-    public static function urlProvider()
+    public function urlProvider()
     {
         $nullIO = new \Composer\IO\NullIO;
 
         return array(
-            array(
-                'http://till:test@svn.example.org/',
-                " --no-auth-cache --username 'till' --password 'test' ",
-            ),
-            array(
-                'http://svn.apache.org/',
-                '',
-            ),
-            array(
-                'svn://johndoe@example.org',
-                " --no-auth-cache --username 'johndoe' --password '' ",
-            ),
+            array('http://till:test@svn.example.org/', $this->getCmd(" --no-auth-cache --username 'till' --password 'test' ")),
+            array('http://svn.apache.org/', ''),
+            array('svn://johndoe@example.org', $this->getCmd(" --no-auth-cache --username 'johndoe' --password '' ")),
         );
     }
 
@@ -99,4 +90,13 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
             $svn->getSvnCommand('svn ls', $url)
         );
     }
+
+    private function getCmd($cmd)
+    {
+        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
+            return strtr($cmd, "'", '"');
+        }
+
+        return $cmd;
+    }
 }

+ 1 - 1
tests/Composer/Test/Repository/VcsRepositoryTest.php

@@ -123,7 +123,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
             'dev-master' => true,
         );
 
-        $repo = new VcsRepository(array('url' => self::$gitRepo), new NullIO);
+        $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO);
         $packages = $repo->getPackages();
         $dumper = new ArrayDumper();
 

+ 2 - 0
tests/Composer/Test/Util/FilesystemTest.php

@@ -41,6 +41,8 @@ class FilesystemTest extends TestCase
             array('/foo/bar', '/foo/baz', true, "dirname(__DIR__).'/baz'"),
             array('/foo/bin/run', '/foo/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"),
             array('/foo/bin/run', '/bar/bin/run', true, "'/bar/bin/run'"),
+            array('/bin/run', '/bin/run', true, "__DIR__"),
+            array('c:/bin/run', 'c:\\bin/run', true, "__DIR__"),
             array('c:/bin/run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"),
             array('c:\\bin\\run', 'c:/vendor/acme/bin/run', true, "dirname(dirname(__DIR__)).'/vendor/acme/bin/run'"),
             array('c:/bin/run', 'd:/vendor/acme/bin/run', true, "'d:/vendor/acme/bin/run'"),

+ 2 - 2
tests/Composer/Test/Util/RemoteFilesystemTest.php

@@ -111,7 +111,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
             $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0);
             $this->fail();
         } catch (\Exception $e) {
-            $this->assertInstanceOf('RuntimeException', $e);
+            $this->assertInstanceOf('Composer\Downloader\TransportException', $e);
             $this->assertContains('URL not found', $e->getMessage());
         }
     }
@@ -137,7 +137,7 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase
             $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0);
             $this->fail();
         } catch (\Exception $e) {
-            $this->assertInstanceOf('RuntimeException', $e);
+            $this->assertInstanceOf('Composer\Downloader\TransportException', $e);
             $this->assertContains('URL required authentication', $e->getMessage());
             $this->assertAttributeEquals(false, 'firstCall', $fs);
         }