diff --git a/bin/compile b/bin/compile old mode 100755 new mode 100644 diff --git a/bin/composer b/bin/composer old mode 100755 new mode 100644 diff --git a/src/Composer/Command/InstallCommand.php b/src/Composer/Command/InstallCommand.php index 392b35d28..086108698 100644 --- a/src/Composer/Command/InstallCommand.php +++ b/src/Composer/Command/InstallCommand.php @@ -111,7 +111,7 @@ EOT $request->install($package->getName(), $constraint); } } else { - $output->writeln('Installing dependencies.'); + $output->writeln('Installing dependencies'); $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); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index c8bcd67bf..affd7f1e1 100644 --- a/src/Composer/Console/Application.php +++ b/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 * @author Jordi Boggiano + * @author François Pluchino */ 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 */ diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index 2b6498965..9afb231fa 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/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(''); } } diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index 4508eb294..0a25fddb8 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/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 * @author Jordi Boggiano + * @author François Pluchino */ 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 " . $package->getName() . " (" . $package->getPrettyVersion() . ")"); 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']); + + if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) { + throw new \RuntimeException('You must enable the openssl extension to use a proxy over https'); } - - $ctx = stream_context_create(array( - 'http' => array( - 'proxy' => $proxy, - 'request_fulluri' => true, - ), - )); - - copy($url, $fileName, $ctx); - } else { - copy($url, $fileName); + + $params['http'] = array( + 'proxy' => $proxy, + 'request_fulluri' => true, + ); } + 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: connection...", 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: $progression%", 80); + } + } + break; + + default: + break; + } + } + /** * Extract file to directory * diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index f30609ca7..453fdcc63 100644 --- a/src/Composer/Factory.php +++ b/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); } } diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php new file mode 100644 index 000000000..cf11f6a57 --- /dev/null +++ b/src/Composer/IO/ConsoleIO.php @@ -0,0 +1,280 @@ + + * Jordi Boggiano + * + * 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 + */ +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; + } +} diff --git a/src/Composer/IO/IOInterface.php b/src/Composer/IO/IOInterface.php new file mode 100644 index 000000000..e5f81facf --- /dev/null +++ b/src/Composer/IO/IOInterface.php @@ -0,0 +1,149 @@ + + * Jordi Boggiano + * + * 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 + */ +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); +} diff --git a/src/Composer/Installer/InstallerInstaller.php b/src/Composer/Installer/InstallerInstaller.php index 7c688ee34..520bde273 100644 --- a/src/Composer/Installer/InstallerInstaller.php +++ b/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) { diff --git a/src/Composer/Installer/LibraryInstaller.php b/src/Composer/Installer/LibraryInstaller.php index d9d7ddd76..25f77e73c 100644 --- a/src/Composer/Installer/LibraryInstaller.php +++ b/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; diff --git a/src/Composer/Repository/RepositoryManager.php b/src/Composer/Repository/RepositoryManager.php index a73934d3f..1ed0f18d0 100644 --- a/src/Composer/Repository/RepositoryManager.php +++ b/src/Composer/Repository/RepositoryManager.php @@ -12,17 +12,26 @@ namespace Composer\Repository; +use Composer\IO\IOInterface; + /** * Repositories manager. * * @author Jordi Boggiano * @author Konstantin Kudryashov + * @author François Pluchino */ 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); } /** diff --git a/src/Composer/Repository/Vcs/GitBitbucketDriver.php b/src/Composer/Repository/Vcs/GitBitbucketDriver.php index 6a15a4fa7..20e060e68 100644 --- a/src/Composer/Repository/Vcs/GitBitbucketDriver.php +++ b/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 */ -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']; diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 1db051313..9d74d1a73 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/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 */ -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); } /** diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 8e7cb46c5..13071a594 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/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 */ -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']; diff --git a/src/Composer/Repository/Vcs/HgBitbucketDriver.php b/src/Composer/Repository/Vcs/HgBitbucketDriver.php index 3594270d6..6d2e8b066 100644 --- a/src/Composer/Repository/Vcs/HgBitbucketDriver.php +++ b/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 */ -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']; diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index b62dfb530..e66fc0a6f 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/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 */ -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)) diff --git a/src/Composer/Repository/Vcs/SvnDriver.php b/src/Composer/Repository/Vcs/SvnDriver.php index 3a3e7c2ed..9ff3ba613 100644 --- a/src/Composer/Repository/Vcs/SvnDriver.php +++ b/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 */ -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); } diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php new file mode 100644 index 000000000..bd7565603 --- /dev/null +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -0,0 +1,148 @@ + + * Jordi Boggiano + * + * 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 + */ +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 " . $this->contentUrl . ":"); + $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; + } + } +} diff --git a/src/Composer/Repository/VcsRepository.php b/src/Composer/Repository/VcsRepository.php index 009f66549..d8839e505 100644 --- a/src/Composer/Repository/VcsRepository.php +++ b/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 @@ -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 ' . $this->packageName . ' (' . $tag . ')'); $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 ' . $this->packageName . ' (' . $branch . ')'); $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)