ソースを参照

Merge remote-tracking branch 'francoispluchino/master'

Conflicts:
	src/Composer/Repository/Vcs/GitDriver.php
	src/Composer/Repository/Vcs/HgDriver.php
	src/Composer/Repository/Vcs/SvnDriver.php
Jordi Boggiano 13 年 前
コミット
6492118f29

+ 0 - 0
bin/compile


+ 0 - 0
bin/composer


+ 2 - 2
src/Composer/Command/InstallCommand.php

@@ -111,7 +111,7 @@ EOT
                 $request->install($package->getName(), $constraint);
             }
         } else {
-            $output->writeln('<info>Installing dependencies.</info>');
+            $output->writeln('<info>Installing dependencies</info>');
 
             $links = $this->collectLinks($input, $composer->getPackage());
 
@@ -123,7 +123,7 @@ EOT
         // prepare solver
         $installationManager = $composer->getInstallationManager();
         $policy              = new DependencyResolver\DefaultPolicy();
-        $solver              = new DependencyResolver\Solver($policy, $pool, $installedRepo);
+        $solver              = new DependencyResolver\Solver($policy, $pool, $installedRepo, $this->getApplication()->getIO());
 
         // solve dependencies
         $operations = $solver->solve($request);

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

@@ -22,16 +22,20 @@ use Symfony\Component\Finder\Finder;
 use Composer\Command;
 use Composer\Composer;
 use Composer\Factory;
+use Composer\IO\IOInterface;
+use Composer\IO\ConsoleIO;
 
 /**
  * The console application that handles the commands
  *
  * @author Ryan Weaver <ryan@knplabs.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
 class Application extends BaseApplication
 {
     protected $composer;
+    protected $io;
 
     public function __construct()
     {
@@ -59,6 +63,7 @@ class Application extends BaseApplication
     public function doRun(InputInterface $input, OutputInterface $output)
     {
         $this->registerCommands();
+        $this->io = new ConsoleIO($input, $output, $this->getHelperSet());
 
         return parent::doRun($input, $output);
     }
@@ -70,9 +75,9 @@ class Application extends BaseApplication
     {
         if (null === $this->composer) {
             try {
-                $this->composer = Factory::create();
+                $this->composer = Factory::create($this->io);
             } catch (\InvalidArgumentException $e) {
-                echo $e->getMessage().PHP_EOL;
+                $this->io->writeln($e->getMessage());
                 exit(1);
             }
         }
@@ -80,6 +85,14 @@ class Application extends BaseApplication
         return $this->composer;
     }
 
+    /**
+     * @return IOInterface
+     */
+    public function getIO()
+    {
+        return $this->io;
+    }
+
     /**
      * Initializes all the composer commands
      */

+ 12 - 8
src/Composer/DependencyResolver/Solver.php

@@ -12,6 +12,7 @@
 
 namespace Composer\DependencyResolver;
 
+use Composer\IO\IOInterface;
 use Composer\Repository\RepositoryInterface;
 use Composer\Package\PackageInterface;
 use Composer\DependencyResolver\Operation;
@@ -55,12 +56,15 @@ class Solver
     protected $packageToUpdateRule = array();
     protected $packageToFeatureRule = array();
 
-    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
+    protected $io;
+
+    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
     {
         $this->policy = $policy;
         $this->pool = $pool;
         $this->installed = $installed;
         $this->rules = new RuleSet;
+        $this->io = $io;
     }
 
     /**
@@ -2046,26 +2050,26 @@ class Solver
 
     public function printDecisionMap()
     {
-        echo "\nDecisionMap: \n";
+        $this->io->writeln("\nDecisionMap: ");
         foreach ($this->decisionMap as $packageId => $level) {
             if ($packageId === 0) {
                 continue;
             }
             if ($level > 0) {
-                echo '    +' . $this->pool->packageById($packageId) . "\n";
+                $this->io->writeln('    +' . $this->pool->packageById($packageId));
             } else {
-                echo '    -' . $this->pool->packageById($packageId) . "\n";
+                $this->io->writeln('    -' . $this->pool->packageById($packageId));
             }
         }
-        echo "\n";
+        $this->io->writeln('');
     }
 
     public function printDecisionQueue()
     {
-        echo "DecisionQueue: \n";
+        $this->io->writeln("DecisionQueue: ");
         foreach ($this->decisionQueue as $i => $literal) {
-            echo '    ' . $literal . ' ' . $this->decisionQueueWhy[$i] . "\n";
+            $this->io->writeln('    ' . $literal . ' ' . $this->decisionQueueWhy[$i]);
         }
-        echo "\n";
+        $this->io->writeln('');
     }
 }

+ 94 - 19
src/Composer/Downloader/FileDownloader.php

@@ -11,6 +11,7 @@
 
 namespace Composer\Downloader;
 
+use Composer\IO\IOInterface;
 use Composer\Package\PackageInterface;
 
 /**
@@ -18,9 +19,23 @@ use Composer\Package\PackageInterface;
  *
  * @author Kirill chEbba Chebunin <iam@chebba.org>
  * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
 abstract class FileDownloader implements DownloaderInterface
 {
+    protected $io;
+    protected $bytesMax;
+
+    /**
+     * Constructor.
+     *
+     * @param IOInterface  $io  The IO instance
+     */
+    public function __construct(IOInterface $io)
+    {
+        $this->io = $io;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -34,6 +49,9 @@ abstract class FileDownloader implements DownloaderInterface
      */
     public function download(PackageInterface $package, $path)
     {
+        // init the progress bar
+        $this->bytesMax = 0;
+
         $url = $package->getDistUrl();
         $checksum = $package->getDistSha1Checksum();
 
@@ -48,7 +66,7 @@ abstract class FileDownloader implements DownloaderInterface
 
         $fileName = rtrim($path.'/'.md5(time().rand()).'.'.pathinfo($url, PATHINFO_EXTENSION), '.');
 
-        echo 'Downloading '.$url.' to '.$fileName.PHP_EOL;
+        $this->io->writeln("  - Package <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
 
         if (!extension_loaded('openssl') && (0 === strpos($url, 'https:') || 0 === strpos($url, 'http://github.com'))) {
             // bypass https for github if openssl is disabled
@@ -59,27 +77,36 @@ abstract class FileDownloader implements DownloaderInterface
             }
         }
 
-        // Handle system proxy
-        if (isset($_SERVER['HTTP_PROXY'])) {
-            // http(s):// is not supported in proxy
-            $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']);
+        // Handle system proxy
+        $params = array('http' => array());
 
-            if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) {
-                throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
-            }
+        if (isset($_SERVER['HTTP_PROXY'])) {
+            // http(s):// is not supported in proxy
+            $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $_SERVER['HTTP_PROXY']);
 
-            $ctx = stream_context_create(array(
-                'http' => array(
-                    'proxy'           => $proxy,
-                    'request_fulluri' => true,
-                ),
-            ));
+            if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) {
+                throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
+            }
+
+            $params['http'] = array(
+                'proxy'           => $proxy,
+                'request_fulluri' => true,
+            );
+        }
 
-            copy($url, $fileName, $ctx);
-        } else {
-            copy($url, $fileName);
+        if ($this->io->hasAuthorization($package->getSourceUrl())) {
+            $auth = $this->io->getAuthorization($package->getSourceUrl());
+            $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
+            $params['http'] = array_merge($params['http'], array('header' => "Authorization: Basic $authStr\r\n"));
         }
 
+        $ctx = stream_context_create($params);
+        stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet')));
+
+        $this->io->overwrite("    Downloading: <comment>connection...</comment>", 80);
+        copy($url, $fileName, $ctx);
+        $this->io->overwriteln("    Downloading", 80);
+
         if (!file_exists($fileName)) {
             throw new \UnexpectedValueException($url.' could not be saved to '.$fileName.', make sure the'
                 .' directory is writable and you have internet connectivity');
@@ -89,11 +116,11 @@ abstract class FileDownloader implements DownloaderInterface
             throw new \UnexpectedValueException('The checksum verification of the archive failed (downloaded from '.$url.')');
         }
 
-        echo 'Unpacking archive'.PHP_EOL;
+        $this->io->writeln('    Unpacking archive');
         $this->extract($fileName, $path);
 
 
-        echo 'Cleaning up'.PHP_EOL;
+        $this->io->writeln('    Cleaning up');
         unlink($fileName);
 
         // If we have only a one dir inside it suppose to be a package itself
@@ -107,6 +134,8 @@ abstract class FileDownloader implements DownloaderInterface
             }
             rmdir($contentDir);
         }
+
+        $this->io->writeln('');
     }
 
     /**
@@ -128,6 +157,52 @@ abstract class FileDownloader implements DownloaderInterface
         $fs->removeDirectory($path);
     }
 
+    /**
+     * Get notification action.
+     *
+     * @param integer $notificationCode The notification code
+     * @param integer $severity         The severity level
+     * @param string  $message          The message
+     * @param integer $messageCode      The message code
+     * @param integer $bytesTransferred The loaded size
+     * @param integer $bytesMax         The total size
+     */
+    protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax)
+    {
+        switch ($notificationCode) {
+            case STREAM_NOTIFY_AUTH_REQUIRED:
+                throw new \LogicException("Authorization is required");
+                break;
+
+            case STREAM_NOTIFY_FAILURE:
+                throw new \LogicException("File not found");
+                break;
+
+            case STREAM_NOTIFY_FILE_SIZE_IS:
+                if ($this->bytesMax < $bytesMax) {
+                    $this->bytesMax = $bytesMax;
+                }
+                break;
+
+            case STREAM_NOTIFY_PROGRESS:
+                if ($this->bytesMax > 0) {
+                    $progression = 0;
+
+                    if ($this->bytesMax > 0) {
+                        $progression = round($bytesTransferred / $this->bytesMax * 100);
+                    }
+
+                    if (0 === $progression % 5) {
+                        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", 80);
+                    }
+                }
+                break;
+
+            default:
+                break;
+        }
+    }
+
     /**
      * Extract file to directory
      *

+ 15 - 14
src/Composer/Factory.php

@@ -13,6 +13,7 @@
 namespace Composer;
 
 use Composer\Json\JsonFile;
+use Composer\IO\IOInterface;
 
 /**
  * Creates an configured instance of composer.
@@ -28,7 +29,7 @@ class Factory
      *
      * @return Composer
      */
-    public function createComposer($composerFile = null)
+    public function createComposer(IOInterface $io, $composerFile = null)
     {
         // load Composer configuration
         if (null === $composerFile) {
@@ -66,13 +67,13 @@ class Factory
         $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir'];
 
         // initialize repository manager
-        $rm = $this->createRepositoryManager($vendorDir);
+        $rm = $this->createRepositoryManager($io, $vendorDir);
 
         // initialize download manager
-        $dm = $this->createDownloadManager();
+        $dm = $this->createDownloadManager($io);
 
         // initialize installation manager
-        $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir);
+        $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io);
 
         // load package
         $loader  = new Package\Loader\RootPackageLoader($rm);
@@ -98,9 +99,9 @@ class Factory
         return $composer;
     }
 
-    protected function createRepositoryManager($vendorDir)
+    protected function createRepositoryManager(IOInterface $io, $vendorDir)
     {
-        $rm = new Repository\RepositoryManager();
+        $rm = new Repository\RepositoryManager($io);
         $rm->setLocalRepository(new Repository\FilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json')));
         $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
         $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
@@ -110,31 +111,31 @@ class Factory
         return $rm;
     }
 
-    protected function createDownloadManager()
+    protected function createDownloadManager(IOInterface $io)
     {
         $dm = new Downloader\DownloadManager();
         $dm->setDownloader('git',  new Downloader\GitDownloader());
         $dm->setDownloader('svn',  new Downloader\SvnDownloader());
         $dm->setDownloader('hg', new Downloader\HgDownloader());
-        $dm->setDownloader('pear', new Downloader\PearDownloader());
-        $dm->setDownloader('zip',  new Downloader\ZipDownloader());
+        $dm->setDownloader('pear', new Downloader\PearDownloader($io));
+        $dm->setDownloader('zip',  new Downloader\ZipDownloader($io));
 
         return $dm;
     }
 
-    protected function createInstallationManager(Repository\RepositoryManager $rm, Downloader\DownloadManager $dm, $vendorDir, $binDir)
+    protected function createInstallationManager(Repository\RepositoryManager $rm, Downloader\DownloadManager $dm, $vendorDir, $binDir, IOInterface $io)
     {
         $im = new Installer\InstallationManager($vendorDir);
-        $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), null));
-        $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $im));
+        $im->addInstaller(new Installer\LibraryInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, null));
+        $im->addInstaller(new Installer\InstallerInstaller($vendorDir, $binDir, $dm, $rm->getLocalRepository(), $io, $im));
 
         return $im;
     }
 
-    static public function create($composerFile = null)
+    static public function create(IOInterface $io, $composerFile = null)
     {
         $factory = new static();
 
-        return $factory->createComposer($composerFile);
+        return $factory->createComposer($io, $composerFile);
     }
 }

+ 280 - 0
src/Composer/IO/ConsoleIO.php

@@ -0,0 +1,280 @@
+<?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\IO;
+
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Formatter\OutputFormatterInterface;
+use Symfony\Component\Console\Helper\HelperSet;
+
+/**
+ * The Input/Output helper.
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ */
+class ConsoleIO implements IOInterface
+{
+    protected $input;
+    protected $output;
+    protected $helperSet;
+    protected $authorizations = array();
+    protected $lastUsername;
+    protected $lastPassword;
+
+    /**
+     * Constructor.
+     *
+     * @param InputInterface  $input     The input instance
+     * @param OutputInterface $output    The output instance
+     * @param HelperSet       $helperSet The helperSet instance
+     */
+    public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet)
+    {
+        $this->input = $input;
+        $this->output = $output;
+        $this->helperSet = $helperSet;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isInteractive()
+    {
+        return $this->input->isInteractive();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function write($messages, $newline = false, $type = 0)
+    {
+        $this->output->write($messages, $newline, $type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function writeln($messages, $type = 0)
+    {
+        $this->output->writeln($messages, $type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function overwrite($messages, $size = 80, $newline = false, $type = 0)
+    {
+        for ($place = $size; $place > 0; $place--) {
+            $this->write("\x08");
+        }
+
+        $this->write($messages, false, $type);
+
+        for ($place = ($size - strlen($messages)); $place > 0; $place--) {
+            $this->write(' ');
+        }
+
+        // clean up the end line
+        for ($place = ($size - strlen($messages)); $place > 0; $place--) {
+            $this->write("\x08");
+        }
+
+        if ($newline) {
+            $this->writeln('');
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function overwriteln($messages, $size = 80, $type = 0)
+    {
+        $this->overwrite($messages, $size, true, $type);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setVerbosity($level)
+    {
+        $this->output->setVerbosity($level);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getVerbosity()
+    {
+        return $this->output->getVerbosity();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setDecorated($decorated)
+    {
+        $this->output->setDecorated($decorated);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isDecorated()
+    {
+        return $this->output->isDecorated();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setFormatter(OutputFormatterInterface $formatter)
+    {
+        $this->output->setFormatter($formatter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getFormatter()
+    {
+        return $this->output->getFormatter();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function ask($question, $default = null)
+    {
+        return $this->helperSet->get('dialog')->ask($this->output, $question, $default);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function askConfirmation($question, $default = true)
+    {
+        return $this->helperSet->get('dialog')->askConfirmation($this->output, $question, $default);
+    }
+
+    public function askAndValidate($question, $validator, $attempts = false, $default = null)
+    {
+        return $this->helperSet->get('dialog')->askAndValidate($this->output, $question, $validator, $attempts, $default);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function askAndHideAnswer($question)
+    {
+        // for windows OS (does not hide the answer in the popup, but it never appears in the STDIN history)
+        if (preg_match('/^win/i', PHP_OS)) {
+            $vbscript = sys_get_temp_dir() . '/prompt_password.vbs';
+            file_put_contents($vbscript,
+                    'wscript.echo(Inputbox("' . addslashes($question) . '","'
+                            . addslashes($question) . '", ""))');
+            $command = "cscript //nologo " . escapeshellarg($vbscript);
+
+            $this->write($question);
+
+            $value = rtrim(shell_exec($command));
+            unlink($vbscript);
+
+            for ($i = 0; $i < strlen($value); ++$i) {
+                $this->write('*');
+            }
+
+            $this->writeln('');
+
+            return $value;
+        }
+
+        // for other OS with shell_exec (hide the answer)
+        if (rtrim(shell_exec($command)) === 'OK') {
+            $command = "/usr/bin/env bash -c 'echo OK'";
+
+            $this->write($question);
+
+            $command = "/usr/bin/env bash -c 'read -s mypassword && echo \$mypassword'";
+            $value = rtrim(shell_exec($command));
+
+            for ($i = 0; $i < strlen($value); ++$i) {
+                $this->write('*');
+            }
+
+            $this->writeln('');
+
+            return $value;
+        }
+
+        // for other OS without shell_exec (does not hide the answer)
+        $this->writeln('');
+
+        return $this->ask($question);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLastUsername()
+    {
+        return $this->lastUsername;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getLastPassword()
+    {
+        return $this->lastPassword;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getAuthorizations()
+    {
+        return $this->authorizations;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function hasAuthorization($repositoryName)
+    {
+        $auths = $this->getAuthorizations();
+        return isset($auths[$repositoryName]);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getAuthorization($repositoryName)
+    {
+        $auths = $this->getAuthorizations();
+        return isset($auths[$repositoryName]) ? $auths[$repositoryName] : array('username' => null, 'password' => null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setAuthorization($repositoryName, $username, $password = null)
+    {
+        $auths = $this->getAuthorizations();
+        $auths[$repositoryName] = array('username' => $username, 'password' => $password);
+
+        $this->authorizations = $auths;
+        $this->lastUsername = $username;
+        $this->lastPassword = $password;
+    }
+}

+ 149 - 0
src/Composer/IO/IOInterface.php

@@ -0,0 +1,149 @@
+<?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\IO;
+
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Helper\HelperSet;
+
+/**
+ * The Input/Output helper interface.
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ */
+interface IOInterface extends OutputInterface
+{
+    /**
+     * Is this input means interactive?
+     *
+     * @return Boolean
+     */
+    function isInteractive();
+
+    /**
+     * Overwrites a previous message to the output.
+     *
+     * @param string|array $messages The message as an array of lines of a single string
+     * @param integer      $size     The size of line
+     * @param Boolean      $newline  Whether to add a newline or not
+     * @param integer      $type     The type of output
+     */
+    function overwrite($messages, $size = 80, $newline = false, $type = 0);
+
+    /**
+     * Overwrites a previous message to the output and adds a newline at the end.
+     *
+     * @param string|array $messages The message as an array of lines of a single string
+     * @param integer      $size     The size of line
+     * @param integer      $type     The type of output
+     */
+    function overwriteln($messages, $size = 80, $type = 0);
+
+    /**
+     * Asks a question to the user.
+     *
+     * @param string|array    $question The question to ask
+     * @param string          $default  The default answer if none is given by the user
+     *
+     * @return string The user answer
+     *
+     * @throws \RuntimeException If there is no data to read in the input stream
+     */
+    function ask($question, $default = null);
+
+    /**
+     * Asks a confirmation to the user.
+     *
+     * The question will be asked until the user answers by nothing, yes, or no.
+     *
+     * @param string|array    $question The question to ask
+     * @param Boolean         $default  The default answer if the user enters nothing
+     *
+     * @return Boolean true if the user has confirmed, false otherwise
+     */
+    function askConfirmation($question, $default = true);
+
+    /**
+     * Asks for a value and validates the response.
+     *
+     * The validator receives the data to validate. It must return the
+     * validated data when the data is valid and throw an exception
+     * otherwise.
+     *
+     * @param string|array    $question  The question to ask
+     * @param callback        $validator A PHP callback
+     * @param integer         $attempts  Max number of times to ask before giving up (false by default, which means infinite)
+     * @param string          $default  The default answer if none is given by the user
+     *
+     * @return mixed
+     *
+     * @throws \Exception When any of the validators return an error
+     */
+    function askAndValidate($question, $validator, $attempts = false, $default = null);
+
+    /**
+     * Asks a question to the user and hide the answer.
+     *
+     * @param string $question The question to ask
+     *
+     * @return string The answer
+     */
+    function askAndHideAnswer($question);
+
+    /**
+     * Get the last username entered.
+     *
+     * @return string The username
+     */
+    function getLastUsername();
+
+    /**
+     * Get the last password entered.
+     *
+     * @return string The password
+     */
+    function getLastPassword();
+
+    /**
+     * Get all authorization informations entered.
+     *
+     * @return array The map of authorization
+     */
+    function getAuthorizations();
+
+    /**
+     * Verify if the repository has a authorization informations.
+     *
+     * @param string $repositoryName The unique name of repository
+     *
+     * @return boolean
+     */
+    function hasAuthorization($repositoryName);
+
+    /**
+     * Get the username and password of repository.
+     *
+     * @param string $repositoryName The unique name of repository
+     *
+     * @return array The 'username' and 'password'
+     */
+    function getAuthorization($repositoryName);
+
+    /**
+     * Set the authorization informations for the repository.
+     *
+     * @param string $repositoryName The unique name of repository
+     * @param string $username       The username
+     * @param string $password       The password
+     */
+    function setAuthorization($repositoryName, $username, $password = null);
+}

+ 4 - 2
src/Composer/Installer/InstallerInstaller.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Installer;
 
+use Composer\IO\IOInterface;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\Downloader\DownloadManager;
 use Composer\Repository\WritableRepositoryInterface;
@@ -33,10 +34,11 @@ class InstallerInstaller extends LibraryInstaller
      * @param   string                      $binDir     relative path for binaries
      * @param   DownloadManager             $dm         download manager
      * @param   WritableRepositoryInterface $repository repository controller
+     * @param   IOInterface                 $io         io instance
      */
-    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, InstallationManager $im)
+    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, IOInterface $io, InstallationManager $im)
     {
-        parent::__construct($vendorDir, $binDir, $dm, $repository, 'composer-installer');
+        parent::__construct($vendorDir, $binDir, $dm, $repository, $io, 'composer-installer');
         $this->installationManager = $im;
 
         foreach ($repository->getPackages() as $package) {

+ 6 - 2
src/Composer/Installer/LibraryInstaller.php

@@ -12,6 +12,7 @@
 
 namespace Composer\Installer;
 
+use Composer\IO\IOInterface;
 use Composer\Downloader\DownloadManager;
 use Composer\Repository\WritableRepositoryInterface;
 use Composer\DependencyResolver\Operation\OperationInterface;
@@ -30,6 +31,7 @@ class LibraryInstaller implements InstallerInterface
     protected $binDir;
     protected $downloadManager;
     protected $repository;
+    protected $io;
     private $type;
     private $filesystem;
 
@@ -40,12 +42,14 @@ class LibraryInstaller implements InstallerInterface
      * @param   string                      $binDir     relative path for binaries
      * @param   DownloadManager             $dm         download manager
      * @param   WritableRepositoryInterface $repository repository controller
+     * @param   IOInterface                 $io         io instance
      * @param   string                      $type       package type that this installer handles
      */
-    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, $type = 'library')
+    public function __construct($vendorDir, $binDir, DownloadManager $dm, WritableRepositoryInterface $repository, IOInterface $io, $type = 'library')
     {
         $this->downloadManager = $dm;
         $this->repository = $repository;
+        $this->io = $io;
         $this->type = $type;
 
         $this->filesystem = new Filesystem();
@@ -136,7 +140,7 @@ class LibraryInstaller implements InstallerInterface
         foreach ($package->getBinaries() as $bin) {
             $link = $this->binDir.'/'.basename($bin);
             if (file_exists($link)) {
-                echo 'Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file'.PHP_EOL;
+                $this->io->writeln('Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file');
                 continue;
             }
             $bin = $this->getInstallPath($package).'/'.$bin;

+ 10 - 1
src/Composer/Repository/RepositoryManager.php

@@ -12,17 +12,26 @@
 
 namespace Composer\Repository;
 
+use Composer\IO\IOInterface;
+
 /**
  * Repositories manager.
  *
  * @author Jordi Boggiano <j.boggiano@seld.be>
  * @author Konstantin Kudryashov <ever.zet@gmail.com>
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
  */
 class RepositoryManager
 {
     private $localRepository;
     private $repositories = array();
     private $repositoryClasses = array();
+    private $io;
+
+    public function __construct(IOInterface $io)
+    {
+        $this->io = $io;
+    }
 
     /**
      * Searches for a package by it's name and version in managed repositories.
@@ -66,7 +75,7 @@ class RepositoryManager
         }
 
         $class = $this->repositoryClasses[$type];
-        return new $class($config);
+        return new $class($config, $this->io);
     }
 
     /**

+ 11 - 10
src/Composer/Repository/Vcs/GitBitbucketDriver.php

@@ -13,13 +13,13 @@
 namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
+use Composer\IO\IOInterface;
 
 /**
  * @author Per Bernhardt <plb@webfactory.de>
  */
-class GitBitbucketDriver implements VcsDriverInterface
+class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
 {
-    protected $url;
     protected $owner;
     protected $repository;
     protected $tags;
@@ -27,12 +27,13 @@ class GitBitbucketDriver implements VcsDriverInterface
     protected $rootIdentifier;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
-        $this->url = $url;
         preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url, $match);
         $this->owner = $match[1];
         $this->repository = $match[2];
+
+        parent::__construct($url, $io);
     }
 
     /**
@@ -48,7 +49,7 @@ class GitBitbucketDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository), true);
+            $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository), true);
             $this->rootIdentifier = !empty($repoData['main_branch']) ? $repoData['main_branch'] : 'master';
         }
 
@@ -79,7 +80,7 @@ class GitBitbucketDriver implements VcsDriverInterface
     public function getDist($identifier)
     {
         $label = array_search($identifier, $this->getTags()) ?: $identifier;
-        $url = 'https://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$label.'.zip';
+        $url = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$label.'.zip';
 
         return array('type' => 'zip', 'url' => $url, 'reference' => $label, 'shasum' => '');
     }
@@ -90,7 +91,7 @@ class GitBitbucketDriver implements VcsDriverInterface
     public function getComposerInformation($identifier)
     {
         if (!isset($this->infoCache[$identifier])) {
-            $composer = @file_get_contents('https://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
+            $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());
             }
@@ -98,7 +99,7 @@ class GitBitbucketDriver implements VcsDriverInterface
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $changeset = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
+                $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
                 $composer['time'] = $changeset['timestamp'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -113,7 +114,7 @@ class GitBitbucketDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
             $this->tags = array();
             foreach ($tagsData as $tag => $data) {
                 $this->tags[$tag] = $data['raw_node'];
@@ -129,7 +130,7 @@ class GitBitbucketDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
             $this->branches = array();
             foreach ($branchData as $branch => $data) {
                 $this->branches[$branch] = $data['raw_node'];

+ 5 - 4
src/Composer/Repository/Vcs/GitDriver.php

@@ -4,22 +4,23 @@ namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
 use Composer\Util\Process;
+use Composer\IO\IOInterface;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GitDriver implements VcsDriverInterface
+class GitDriver extends VcsDriver implements VcsDriverInterface
 {
-    protected $url;
     protected $tags;
     protected $branches;
     protected $rootIdentifier;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
-        $this->url = $url;
         $this->tmpDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/';
+
+        parent::__construct($url, $io);
     }
 
     /**

+ 12 - 9
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -3,11 +3,12 @@
 namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
+use Composer\IO\IOInterface;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class GitHubDriver implements VcsDriverInterface
+class GitHubDriver extends VcsDriver implements VcsDriverInterface
 {
     protected $owner;
     protected $repository;
@@ -16,11 +17,13 @@ class GitHubDriver implements VcsDriverInterface
     protected $rootIdentifier;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
         preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url, $match);
         $this->owner = $match[1];
         $this->repository = $match[2];
+
+        parent::__construct($url, $io);
     }
 
     /**
@@ -36,7 +39,7 @@ class GitHubDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode(file_get_contents('https://api.github.com/repos/'.$this->owner.'/'.$this->repository), true);
+            $repoData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository), true);
             $this->rootIdentifier = $repoData['master_branch'] ?: 'master';
         }
 
@@ -48,7 +51,7 @@ class GitHubDriver implements VcsDriverInterface
      */
     public function getUrl()
     {
-        return 'http://github.com/'.$this->owner.'/'.$this->repository.'.git';
+        return $this->url;
     }
 
     /**
@@ -67,7 +70,7 @@ class GitHubDriver implements VcsDriverInterface
     public function getDist($identifier)
     {
         $label = array_search($identifier, $this->getTags()) ?: $identifier;
-        $url = 'http://github.com/'.$this->owner.'/'.$this->repository.'/zipball/'.$label;
+        $url = $this->getScheme() . '://github.com/'.$this->owner.'/'.$this->repository.'/zipball/'.$label;
 
         return array('type' => 'zip', 'url' => $url, 'reference' => $label, 'shasum' => '');
     }
@@ -78,7 +81,7 @@ class GitHubDriver implements VcsDriverInterface
     public function getComposerInformation($identifier)
     {
         if (!isset($this->infoCache[$identifier])) {
-            $composer = @file_get_contents('https://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json');
+            $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());
             }
@@ -86,7 +89,7 @@ class GitHubDriver implements VcsDriverInterface
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $commit = json_decode(file_get_contents('https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier), true);
+                $commit = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/commits/'.$identifier), true);
                 $composer['time'] = $commit['commit']['committer']['date'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -101,7 +104,7 @@ class GitHubDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode(file_get_contents('https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags'), true);
             $this->tags = array();
             foreach ($tagsData as $tag) {
                 $this->tags[$tag['name']] = $tag['commit']['sha'];
@@ -117,7 +120,7 @@ class GitHubDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode(file_get_contents('https://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = json_decode($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches'), true);
             $this->branches = array();
             foreach ($branchData as $branch) {
                 $this->branches[$branch['name']] = $branch['commit']['sha'];

+ 11 - 10
src/Composer/Repository/Vcs/HgBitbucketDriver.php

@@ -13,13 +13,13 @@
 namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
+use Composer\IO\IOInterface;
 
 /**
  * @author Per Bernhardt <plb@webfactory.de>
  */
-class HgBitbucketDriver implements VcsDriverInterface
+class HgBitbucketDriver extends VcsDriver implements VcsDriverInterface
 {
-    protected $url;
     protected $owner;
     protected $repository;
     protected $tags;
@@ -27,12 +27,13 @@ class HgBitbucketDriver implements VcsDriverInterface
     protected $rootIdentifier;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
-        $this->url = $url;
         preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url, $match);
         $this->owner = $match[1];
         $this->repository = $match[2];
+
+        parent::__construct($url, $io);
     }
 
     /**
@@ -48,7 +49,7 @@ class HgBitbucketDriver implements VcsDriverInterface
     public function getRootIdentifier()
     {
         if (null === $this->rootIdentifier) {
-            $repoData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $repoData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
             $this->rootIdentifier = $repoData['tip']['raw_node'];
         }
 
@@ -79,7 +80,7 @@ class HgBitbucketDriver implements VcsDriverInterface
     public function getDist($identifier)
     {
         $label = array_search($identifier, $this->getTags()) ?: $identifier;
-        $url = 'https://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$label.'.zip';
+        $url = $this->getScheme() . '://bitbucket.org/'.$this->owner.'/'.$this->repository.'/get/'.$label.'.zip';
 
         return array('type' => 'zip', 'url' => $url, 'reference' => $label, 'shasum' => '');
     }
@@ -90,7 +91,7 @@ class HgBitbucketDriver implements VcsDriverInterface
     public function getComposerInformation($identifier)
     {
         if (!isset($this->infoCache[$identifier])) {
-            $composer = @file_get_contents('https://bitbucket.org/'.$this->owner.'/'.$this->repository.'/raw/'.$identifier.'/composer.json');
+            $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());
             }
@@ -98,7 +99,7 @@ class HgBitbucketDriver implements VcsDriverInterface
             $composer = JsonFile::parseJson($composer);
 
             if (!isset($composer['time'])) {
-                $changeset = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
+                $changeset = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier), true);
                 $composer['time'] = $changeset['timestamp'];
             }
             $this->infoCache[$identifier] = $composer;
@@ -113,7 +114,7 @@ class HgBitbucketDriver implements VcsDriverInterface
     public function getTags()
     {
         if (null === $this->tags) {
-            $tagsData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
+            $tagsData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/tags'), true);
             $this->tags = array();
             foreach ($tagsData as $tag => $data) {
                 $this->tags[$tag] = $data['raw_node'];
@@ -129,7 +130,7 @@ class HgBitbucketDriver implements VcsDriverInterface
     public function getBranches()
     {
         if (null === $this->branches) {
-            $branchData = json_decode(file_get_contents('https://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
+            $branchData = json_decode($this->getContents($this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/branches'), true);
             $this->branches = array();
             foreach ($branchData as $branch => $data) {
                 $this->branches[$branch] = $data['raw_node'];

+ 7 - 6
src/Composer/Repository/Vcs/HgDriver.php

@@ -14,22 +14,23 @@ namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
 use Composer\Util\Process;
+use Composer\IO\IOInterface;
 
 /**
  * @author Per Bernhardt <plb@webfactory.de>
  */
-class HgDriver implements VcsDriverInterface
+class HgDriver extends VcsDriver implements VcsDriverInterface
 {
-    protected $url;
     protected $tags;
     protected $branches;
     protected $rootIdentifier;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
-        $this->url = $url;
         $this->tmpDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/';
+
+        parent::__construct($url, $io);
     }
 
     /**
@@ -59,7 +60,7 @@ class HgDriver implements VcsDriverInterface
             Process::execute(sprintf('cd %s && hg tip --template "{node}"', $tmpDir), $output);
             $this->rootIdentifier = $output[0];
         }
-        
+
         return $this->rootIdentifier;
     }
 
@@ -123,7 +124,7 @@ class HgDriver implements VcsDriverInterface
     {
         if (null === $this->tags) {
             $tags = array();
-            
+
             Process::execute(sprintf('cd %s && hg tags', escapeshellarg($this->tmpDir)), $output);
             foreach ($output as $tag) {
                 if (preg_match('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match))

+ 5 - 4
src/Composer/Repository/Vcs/SvnDriver.php

@@ -4,21 +4,22 @@ namespace Composer\Repository\Vcs;
 
 use Composer\Json\JsonFile;
 use Composer\Util\Process;
+use Composer\IO\IOInterface;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-class SvnDriver implements VcsDriverInterface
+class SvnDriver extends VcsDriver implements VcsDriverInterface
 {
-    protected $url;
     protected $baseUrl;
     protected $tags;
     protected $branches;
     protected $infoCache = array();
 
-    public function __construct($url)
+    public function __construct($url, IOInterface $io)
     {
-        $this->url = $this->baseUrl = rtrim($url, '/');
+        parent::__construct($this->baseUrl = rtrim($url, '/'), $io);
+
         if (false !== ($pos = strrpos($url, '/trunk'))) {
             $this->baseUrl = substr($url, 0, $pos);
         }

+ 148 - 0
src/Composer/Repository/Vcs/VcsDriver.php

@@ -0,0 +1,148 @@
+<?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\Repository\Vcs;
+
+use Composer\IO\IOInterface;
+
+/**
+ * A driver implementation for driver with authorization interaction.
+ *
+ * @author François Pluchino <francois.pluchino@opendisplay.com>
+ */
+abstract class VcsDriver
+{
+    protected $url;
+    protected $io;
+    private $firstCall;
+    private $contentUrl;
+    private $content;
+
+    /**
+     * Constructor.
+     *
+     * @param string      $url The URL
+     * @param IOInterface $io  The IO instance
+     */
+    public function __construct($url, IOInterface $io)
+    {
+        $this->url = $url;
+        $this->io = $io;
+        $this->firstCall = true;
+    }
+
+    /**
+     * Get the https or http protocol.
+     *
+     * @return string The correct type of protocol
+     */
+    protected function getScheme()
+    {
+        if (extension_loaded('openssl')) {
+            return 'https';
+        }
+        return 'http';
+    }
+
+    /**
+     * Get the remote content.
+     *
+     * @param string $url The URL of content
+     *
+     * @return mixed The result
+     */
+    protected function getContents($url)
+    {
+        $this->contentUrl = $url;
+        $auth = $this->io->getAuthorization($this->url);
+        $params = array();
+
+        // add authorization to curl options
+        if ($this->io->hasAuthorization($this->url)) {
+            $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
+            $params['http'] = array('header' => "Authorization: Basic $authStr\r\n");
+
+        } else if (null !== $this->io->getLastUsername()) {
+            $authStr = base64_encode($this->io->getLastUsername() . ':' . $this->io->getLastPassword());
+            $params['http'] = array('header' => "Authorization: Basic $authStr\r\n");
+        }
+
+        $ctx = stream_context_create($params);
+        stream_context_set_params($ctx, array("notification" => array($this, 'callbackGet')));
+
+        $content = @file_get_contents($url, false, $ctx);
+
+        // content get after authorization
+        if (false === $content) {
+            $content = $this->content;
+            $this->content = null;
+            $this->contentUrl = null;
+        }
+
+        return $content;
+    }
+
+    /**
+     * Get notification action.
+     *
+     * @param integer $notificationCode The notification code
+     * @param integer $severity         The severity level
+     * @param string  $message          The message
+     * @param integer $messageCode      The message code
+     * @param integer $bytesTransferred The loaded size
+     * @param integer $bytesMax         The total size
+     */
+    protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax)
+    {
+        switch ($notificationCode) {
+            case STREAM_NOTIFY_AUTH_REQUIRED:
+            case STREAM_NOTIFY_FAILURE:
+                // for private repository returning 404 error when the authorization is incorrect
+                $auth = $this->io->getAuthorization($this->url);
+                $ps = $this->firstCall && 404 === $messageCode
+                        && null === $this->io->getLastUsername()
+                        && null === $auth['username'];
+
+                if (404 === $messageCode && !$this->firstCall) {
+                    throw new \RuntimeException("The '" . $this->contentUrl . "' URL not found");
+                }
+
+                if ($this->firstCall) {
+                    $this->firstCall = false;
+                }
+
+                // get authorization informations
+                if (401 === $messageCode || $ps) {
+                    if (!$this->io->isInteractive()) {
+                        $mess = "The '" . $this->contentUrl . "' URL not found";
+
+                        if (401 === $code || $ps) {
+                            $mess = "The '" . $this->contentUrl . "' URL required the authorization.\nYou must be used the interactive console";
+                        }
+
+                        throw new \RuntimeException($mess);
+                    }
+
+                    $this->io->writeln("Authorization for <info>" . $this->contentUrl . "</info>:");
+                    $username = $this->io->ask('    Username: ');
+                    $password = $this->io->askAndHideAnswer('    Password: ');
+                    $this->io->setAuthorization($this->url, $username, $password);
+
+                    $this->content = $this->getContents($this->contentUrl);
+                }
+                break;
+
+            default:
+                break;
+        }
+    }
+}

+ 21 - 12
src/Composer/Repository/VcsRepository.php

@@ -5,6 +5,7 @@ namespace Composer\Repository;
 use Composer\Repository\Vcs\VcsDriverInterface;
 use Composer\Package\Version\VersionParser;
 use Composer\Package\Loader\ArrayLoader;
+use Composer\IO\IOInterface;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -13,9 +14,10 @@ class VcsRepository extends ArrayRepository
 {
     protected $url;
     protected $packageName;
-    protected $debug;
+    protected $debug;
+    protected $io;
 
-    public function __construct(array $config, array $drivers = null)
+    public function __construct(array $config, IOInterface $io, array $drivers = null)
     {
         if (!filter_var($config['url'], FILTER_VALIDATE_URL)) {
             throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']);
@@ -31,6 +33,7 @@ class VcsRepository extends ArrayRepository
         );
 
         $this->url = $config['url'];
+        $this->io = $io;
     }
 
     public function setDebug($debug)
@@ -42,7 +45,7 @@ class VcsRepository extends ArrayRepository
     {
         foreach ($this->drivers as $driver) {
             if ($driver::supports($this->url)) {
-                $driver = new $driver($this->url);
+                $driver = new $driver($this->url, $this->io);
                 $driver->initialize();
                 return $driver;
             }
@@ -50,7 +53,7 @@ class VcsRepository extends ArrayRepository
 
         foreach ($this->drivers as $driver) {
             if ($driver::supports($this->url, true)) {
-                $driver = new $driver($this->url);
+                $driver = new $driver($this->url, $this->io);
                 $driver->initialize();
                 return $driver;
             }
@@ -77,6 +80,7 @@ class VcsRepository extends ArrayRepository
         }
 
         foreach ($driver->getTags() as $tag => $identifier) {
+            $this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)');
             $parsedTag = $this->validateTag($versionParser, $tag);
             if ($parsedTag && $driver->hasComposerFile($identifier)) {
                 try {
@@ -84,7 +88,7 @@ class VcsRepository extends ArrayRepository
                 } catch (\Exception $e) {
                     if (strpos($e->getMessage(), 'JSON Parse Error') !== false) {
                         if ($debug) {
-                            echo 'Skipped tag '.$tag.', '.$e->getMessage().PHP_EOL;
+                            $this->io->writeln('Skipped tag '.$tag.', '.$e->getMessage());
                         }
                         continue;
                     } else {
@@ -108,22 +112,25 @@ class VcsRepository extends ArrayRepository
                 // broken package, version doesn't match tag
                 if ($data['version_normalized'] !== $parsedTag) {
                     if ($debug) {
-                        echo 'Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json'.PHP_EOL;
+                        $this->io->writeln('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json');
                     }
                     continue;
                 }
 
                 if ($debug) {
-                    echo 'Importing tag '.$tag.' ('.$data['version_normalized'].')'.PHP_EOL;
+                    $this->io->writeln('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                 }
 
                 $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
             } elseif ($debug) {
-                echo 'Skipped tag '.$tag.', '.($parsedTag ? 'no composer file was found' : 'invalid name').PHP_EOL;
+                $this->io->writeln('Skipped tag '.$tag.', '.($parsedTag ? 'no composer file was found' : 'invalid name'));
             }
         }
 
+        $this->io->overwrite('');
+
         foreach ($driver->getBranches() as $branch => $identifier) {
+            $this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)');
             $parsedBranch = $this->validateBranch($versionParser, $branch);
             if ($driver->hasComposerFile($identifier)) {
                 $data = $driver->getComposerInformation($identifier);
@@ -137,7 +144,7 @@ class VcsRepository extends ArrayRepository
                     $data['version_normalized'] = $parsedBranch;
                 } else {
                     if ($debug) {
-                        echo 'Skipped branch '.$branch.', invalid name and no composer file was found'.PHP_EOL;
+                        $this->io->writeln('Skipped branch '.$branch.', invalid name and no composer file was found');
                     }
                     continue;
                 }
@@ -151,7 +158,7 @@ class VcsRepository extends ArrayRepository
                 foreach ($this->getPackages() as $package) {
                     if ($normalizedStableVersion === $package->getVersion()) {
                         if ($debug) {
-                            echo 'Skipped branch '.$branch.', already tagged'.PHP_EOL;
+                            $this->io->writeln('Skipped branch '.$branch.', already tagged');
                         }
 
                         continue 2;
@@ -159,14 +166,16 @@ class VcsRepository extends ArrayRepository
                 }
 
                 if ($debug) {
-                    echo 'Importing branch '.$branch.' ('.$data['version_normalized'].')'.PHP_EOL;
+                    $this->io->writeln('Importing branch '.$branch.' ('.$data['version_normalized'].')');
                 }
 
                 $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
             } elseif ($debug) {
-                echo 'Skipped branch '.$branch.', no composer file was found'.PHP_EOL;
+                $this->io->writeln('Skipped branch '.$branch.', no composer file was found');
             }
         }
+
+        $this->io->overwrite('');
     }
 
     private function preProcess(VcsDriverInterface $driver, array $data, $identifier)