1
0
Fork 0

Merge remote-tracking branch 'github-composer/2.0' into solve-without-installed

* github-composer/2.0: (48 commits)
  Fix missing use/undefined var
  Split up steps on VCS downloaders to allow doing network operations before touching the filesystem on GitDownloader, fixes #7903
  Fix use statement
  Deduplicate findHeaderValue code
  Add install-path to the installed.json for every package, fixes #2174, closes #2424
  Remove unnecessary config from phpstan
  Make sure the directory exists and will not block installation later when downloading
  Avoid wiping the whole target package if download of the new one fails, refs #7929
  Only empty dir before actually installing packages, fixes #7929
  Improve output when installing packages
  Show best possible version in diagnose command
  Remove extra arg
  Allow path repos to point to their own source dir as install target, resulting in noop, fixes #8254
  Fix use of decodeJson
  Fix update mirrors to also update transport-options, fixes #7672
  Fix updating or URLs to include dist type and shasum, fixes #8216
  Fix origin computation
  Improve handling of non-standard ports for GitLab and GitHub installs, fixes #8173
  Load packages from the lock file for check-platform-reqs if no dependencies have been installed yet, fixes #8058
  Fix error_handler return type declaration
  ...
pull/7936/head
Nils Adermann 2019-09-07 02:55:21 +02:00
commit f5e18250e6
91 changed files with 1571 additions and 522 deletions

View File

@ -30,9 +30,11 @@ matrix:
env:
- deps=high
- php: nightly
- php: 7.4snapshot
fast_finish: true
allow_failures:
- php: nightly
- php: 7.4snapshot
before_install:
# disable xdebug if available
@ -62,7 +64,7 @@ script:
- ls -d tests/Composer/Test/* | grep -v TestCase.php | parallel --gnu --keep-order 'echo "Running {} tests"; ./vendor/bin/phpunit -c tests/complete.phpunit.xml --colors=always {} || (echo -e "\e[41mFAILED\e[0m {}" && exit 1);'
# Run PHPStan
- if [[ $PHPSTAN == "1" ]]; then
composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs &&
bin/composer require --dev phpstan/phpstan-shim:^0.11 --ignore-platform-reqs &&
vendor/bin/phpstan.phar analyse src tests --configuration=phpstan/config.neon --autoload-file=phpstan/autoload.php;
fi

View File

@ -259,6 +259,10 @@ match the platform requirements of the installed packages. This can be used
to verify that a production server has all the extensions needed to run a
project after installing it for example.
Unlike update/install, this command will ignore config.platform settings and
check the real platform packages so you can be certain you have the required
platform dependencies.
## global
The global command allows you to run other commands like `install`, `remove`, `require`

View File

@ -112,6 +112,19 @@ Note that this will still need to pull and scan all of your VCS repositories
because any VCS repository might contain (on any branch) one of the selected
packages.
If you want to scan only the selected package and not all VCS repositories you need
to declare a *name* for all your package (this only work on VCS repositories type) :
```json
{
"repositories": [
{ "name": "company/privaterepo", "type": "vcs", "url": "https://github.com/mycompany/privaterepo" },
{ "name": "private/repo", "type": "vcs", "url": "http://svn.example.org/private/repo" },
{ "name": "mycompany/privaterepo2", "type": "vcs", "url": "https://github.com/mycompany/privaterepo2" }
]
}
```
If you want to scan only a single repository and update all packages found in
it, pass the VCS repository URL as an optional argument:

View File

@ -16,7 +16,6 @@ parameters:
- '~^Anonymous function has an unused use \$io\.$~'
- '~^Anonymous function has an unused use \$cache\.$~'
- '~^Anonymous function has an unused use \$path\.$~'
- '~^Anonymous function has an unused use \$fileName\.$~'
# ion cube is not installed
- '~^Function ioncube_loader_\w+ not found\.$~'

View File

@ -34,6 +34,8 @@ class CheckPlatformReqsCommand extends BaseCommand
<<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages.
Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.
<info>php composer.phar check-platform-reqs</info>
EOT
@ -49,6 +51,10 @@ EOT
$dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
} else {
$dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
// fallback to lockfile if installed repo is empty
if (!$dependencies) {
$dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
}
$requires += $composer->getPackage()->getDevRequires();
}
foreach ($requires as $require => $link) {

View File

@ -156,7 +156,7 @@ EOT
$this->outputResult($this->checkVersion($config));
}
$io->write(sprintf('Composer version: <comment>%s</comment>', Composer::VERSION));
$io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));
$platformOverrides = $config->get('platform') ?: array();
$platformRepo = new PlatformRepository(array(), $platformOverrides);
@ -254,7 +254,7 @@ EOT
$protocol = extension_loaded('openssl') ? 'https' : 'http';
try {
$json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->parseJson();
$json = $this->httpDownloader->get($protocol . '://repo.packagist.org/packages.json')->decodeJson();
$hash = reset($json['provider-includes']);
$hash = $hash['sha256'];
$path = str_replace('%hash%', $hash, key($json['provider-includes']));
@ -375,7 +375,7 @@ EOT
}
$url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
$data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->parseJson();
$data = $this->httpDownloader->get($url, array('retry-auth-failure' => false))->decodeJson();
return $data['resources']['core'];
}

View File

@ -168,13 +168,25 @@ EOT
if ($repositories) {
$config = Factory::createConfig($io);
$repos = array(new PlatformRepository);
$createDefaultPackagistRepo = true;
foreach ($repositories as $repo) {
$repos[] = RepositoryFactory::fromString($io, $config, $repo);
$repoConfig = RepositoryFactory::configFromString($io, $config, $repo);
if (
(isset($repoConfig['packagist']) && $repoConfig === array('packagist' => false))
|| (isset($repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
) {
$createDefaultPackagistRepo = false;
continue;
}
$repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig);
}
if ($createDefaultPackagistRepo) {
$repos[] = RepositoryFactory::createRepo($io, $config, array(
'type' => 'composer',
'url' => 'https://repo.packagist.org',
));
}
$repos[] = RepositoryFactory::createRepo($io, $config, array(
'type' => 'composer',
'url' => 'https://repo.packagist.org',
));
$this->repos = new CompositeRepository($repos);
unset($repos, $config, $repositories);

View File

@ -26,6 +26,7 @@ use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Util\Silencer;
/**
* @author Jérémy Romey <jeremy@free-agent.fr>
@ -103,11 +104,6 @@ EOT
return 1;
}
if (!is_writable($this->file)) {
$io->writeError('<error>'.$this->file.' is not writable.</error>');
return 1;
}
if (filesize($this->file) === 0) {
file_put_contents($this->file, "{\n}\n");
@ -116,6 +112,14 @@ EOT
$this->json = new JsonFile($this->file);
$this->composerBackup = file_get_contents($this->json->getPath());
// check for writability by writing to the file as is_writable can not be trusted on network-mounts
// see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
if (!is_writable($this->file) && !Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
$io->writeError('<error>'.$this->file.' is not writable.</error>');
return 1;
}
$composer = $this->getComposer(true, $input->getOption('no-plugins'));
$repos = $composer->getRepositoryManager()->getRepositories();
@ -141,7 +145,12 @@ EOT
// validate requirements format
$versionParser = new VersionParser();
foreach ($requirements as $constraint) {
foreach ($requirements as $package => $constraint) {
if (strtolower($package) === $composer->getPackage()->getName()) {
$io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));
return 1;
}
$versionParser->parseConstraints($constraint);
}

View File

@ -379,6 +379,9 @@ class Application extends BaseApplication
public function resetComposer()
{
$this->composer = null;
if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) {
$this->getIO()->resetAuthentications();
}
}
/**

View File

@ -195,7 +195,7 @@ class RuleSetGenerator
}
}
protected function addConflictRules()
protected function addConflictRules($ignorePlatformReqs = false)
{
/** @var PackageInterface $package */
foreach ($this->addedPackages as $package) {
@ -204,6 +204,10 @@ class RuleSetGenerator
continue;
}
if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
continue;
}
/** @var PackageInterface $possibleConflict */
foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
$conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);
@ -305,7 +309,7 @@ class RuleSetGenerator
$this->addRulesForRequest($request, $ignorePlatformReqs);
$this->addConflictRules();
$this->addConflictRules($ignorePlatformReqs);
// Remove references to packages
$this->addedPackages = $this->addedPackagesByNames = null;

View File

@ -33,16 +33,16 @@ abstract class ArchiveDownloader extends FileDownloader
public function install(PackageInterface $package, $path, $output = true)
{
if ($output) {
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): Extracting archive");
} else {
$this->io->writeError('Extracting archive', false);
}
$this->filesystem->emptyDirectory($path);
$temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
$fileName = $this->getFileName($package, $path);
if ($output) {
$this->io->writeError(' Extracting archive', true, IOInterface::VERBOSE);
}
try {
$this->filesystem->ensureDirectoryExists($temporaryDir);
try {

View File

@ -165,9 +165,9 @@ class DownloadManager
/**
* Downloads package into target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* @param PackageInterface $prevPackage previous package instance in case of updates
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* @param PackageInterface|null $prevPackage previous package instance in case of updates
*
* @return PromiseInterface
* @throws \InvalidArgumentException if package have no urls to download from
@ -182,7 +182,7 @@ class DownloadManager
$io = $this->io;
$self = $this;
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download) {
$download = function ($retry = false) use (&$sources, $io, $package, $self, $targetDir, &$download, $prevPackage) {
$source = array_shift($sources);
if ($retry) {
$io->writeError(' <warning>Now trying to download from ' . $source . '</warning>');
@ -214,7 +214,7 @@ class DownloadManager
};
try {
$result = $downloader->download($package, $targetDir);
$result = $downloader->download($package, $targetDir, $prevPackage);
} catch (\Exception $e) {
return $handleError($e);
}
@ -232,12 +232,31 @@ class DownloadManager
return $download();
}
/**
* Prepares an operation execution
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* @param PackageInterface|null $prevPackage previous package instance in case of updates
*
* @return PromiseInterface|null
*/
public function prepare($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
{
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
return $downloader->prepare($type, $package, $targetDir, $prevPackage);
}
}
/**
* Installs package into target dir.
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
*
* @return PromiseInterface|null
* @throws \InvalidArgumentException if package have no urls to download from
* @throws \RuntimeException
*/
@ -245,7 +264,7 @@ class DownloadManager
{
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
$downloader->install($package, $targetDir);
return $downloader->install($package, $targetDir);
}
}
@ -256,6 +275,7 @@ class DownloadManager
* @param PackageInterface $target target package version
* @param string $targetDir target dir
*
* @return PromiseInterface|null
* @throws \InvalidArgumentException if initial package is not installed
*/
public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
@ -270,17 +290,14 @@ class DownloadManager
// if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
if (!$downloader) {
$initialDownloader->remove($initial, $targetDir);
return;
return $initialDownloader->remove($initial, $targetDir);
}
$initialType = $this->getDownloaderType($initialDownloader);
$targetType = $this->getDownloaderType($downloader);
if ($initialType === $targetType) {
try {
$downloader->update($initial, $target, $targetDir);
return;
return $downloader->update($initial, $target, $targetDir);
} catch (\RuntimeException $e) {
if (!$this->io->isInteractive()) {
throw $e;
@ -294,8 +311,15 @@ class DownloadManager
// if downloader type changed, or update failed and user asks for reinstall,
// we wipe the dir and do a new install instead of updating it
$initialDownloader->remove($initial, $targetDir);
$this->install($target, $targetDir);
$promise = $initialDownloader->remove($initial, $targetDir);
if ($promise) {
$self = $this;
return $promise->then(function ($res) use ($self, $target, $targetDir) {
return $self->install($target, $targetDir);
});
}
return $this->install($target, $targetDir);
}
/**
@ -303,12 +327,32 @@ class DownloadManager
*
* @param PackageInterface $package package instance
* @param string $targetDir target dir
*
* @return PromiseInterface|null
*/
public function remove(PackageInterface $package, $targetDir)
{
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
$downloader->remove($package, $targetDir);
return $downloader->remove($package, $targetDir);
}
}
/**
* Cleans up a failed operation
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param string $targetDir target dir
* @param PackageInterface|null $prevPackage previous package instance in case of updates
*
* @return PromiseInterface|null
*/
public function cleanup($type, PackageInterface $package, $targetDir, PackageInterface $prevPackage = null)
{
$downloader = $this->getDownloaderForPackage($package);
if ($downloader) {
return $downloader->cleanup($type, $package, $targetDir, $prevPackage);
}
}

View File

@ -31,14 +31,30 @@ interface DownloaderInterface
public function getInstallationSource();
/**
* This should do any network-related tasks to prepare for install/update
* This should do any network-related tasks to prepare for an upcoming install/update
*
* @return PromiseInterface|null
*/
public function download(PackageInterface $package, $path);
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null);
/**
* Downloads specific package into specific folder.
* Do anything that needs to be done between all downloads have been completed and the actual operation is executed
*
* All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
* for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
* user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
* be undone as much as possible.
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param string $path download path
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface|null
*/
public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null);
/**
* Installs specific package into specific folder.
*
* @param PackageInterface $package package instance
* @param string $path download path
@ -61,4 +77,19 @@ interface DownloaderInterface
* @param string $path download path
*/
public function remove(PackageInterface $package, $path);
/**
* Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
*
* Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
* all installers a change to cleanup things they did previously, so you need to keep track of changes
* applied in the installer/downloader themselves.
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param string $path download path
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface|null
*/
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null);
}

View File

@ -84,7 +84,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, $path, $output = true)
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
{
if (!$package->getDistUrl()) {
throw new \InvalidArgumentException('The given package is missing url information');
@ -101,7 +101,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
);
}
$this->filesystem->emptyDirectory($path);
$this->filesystem->ensureDirectoryExists($path);
$fileName = $this->getFileName($package, $path);
$io = $this->io;
@ -176,7 +176,9 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$reject = function ($e) use ($io, &$urls, $download, $fileName, $path, $package, &$retries, $filesystem, $self) {
// clean up
$filesystem->removeDirectory($path);
if (file_exists($fileName)) {
$filesystem->unlink($fileName);
}
$self->clearLastCacheWrite($package);
if ($e instanceof TransportException) {
@ -220,6 +222,20 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
return $download();
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
@ -229,6 +245,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
}
$this->filesystem->emptyDirectory($path);
$this->filesystem->ensureDirectoryExists($path);
$this->filesystem->rename($this->getFileName($package, $path), $path . pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME));
}
@ -333,7 +350,7 @@ class FileDownloader implements DownloaderInterface, ChangeReportInterface
$e = null;
try {
$res = $this->download($package, $targetDir.'_compare', false);
$res = $this->download($package, $targetDir.'_compare', null, false);
$this->httpDownloader->wait();
$res = $this->install($package, $targetDir.'_compare', false);

View File

@ -23,7 +23,15 @@ class FossilDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doInstall(PackageInterface $package, $path, $url)
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
protected function doInstall(PackageInterface $package, $path, $url)
{
// Ensure we are allowed to use this URL by config
$this->config->prohibitUrlByConfig($url, $this->io);
@ -49,7 +57,7 @@ class FossilDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{
// Ensure we are allowed to use this URL by config
$this->config->prohibitUrlByConfig($url, $this->io);

View File

@ -29,6 +29,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
private $hasStashedChanges = false;
private $hasDiscardedChanges = false;
private $gitUtil;
private $cachedPackages = array();
public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
{
@ -39,7 +40,28 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
/**
* {@inheritDoc}
*/
public function doInstall(PackageInterface $package, $path, $url)
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{
GitUtil::cleanEnv();
$cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/';
$gitVersion = $this->gitUtil->getVersion();
// --dissociate option is only available since git 2.3.0-rc0
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
$this->io->writeError(" - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
$ref = $package->getSourceReference();
if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref) && is_dir($cachePath)) {
$this->cachedPackages[$package->getId()][$ref] = true;
}
}
}
/**
* {@inheritDoc}
*/
protected function doInstall(PackageInterface $package, $path, $url)
{
GitUtil::cleanEnv();
$path = $this->normalizePath($path);
@ -47,26 +69,20 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$ref = $package->getSourceReference();
$flag = Platform::isWindows() ? '/D ' : '';
// --dissociate option is only available since git 2.3.0-rc0
$gitVersion = $this->gitUtil->getVersion();
$msg = "Cloning ".$this->getShortHash($ref);
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer';
if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
$this->io->writeError('', true, IOInterface::DEBUG);
$this->io->writeError(sprintf(' Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
try {
$this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref);
if (is_dir($cachePath)) {
$command =
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
. '&& cd '.$flag.'%path% '
. '&& git remote set-url origin %url% && git remote add composer %url%';
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
}
} catch (\RuntimeException $e) {
if (!empty($this->cachedPackages[$package->getId()][$ref])) {
$msg = "Cloning ".$this->getShortHash($ref).' from cache';
$command =
'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
. '&& cd '.$flag.'%path% '
. '&& git remote set-url origin %url% && git remote add composer %url%';
} else {
$msg = "Cloning ".$this->getShortHash($ref);
$command = 'git clone --no-checkout %url% %path% && cd '.$flag.'%path% && git remote add composer %url% && git fetch composer';
if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
}
}
$this->io->writeError($msg);
$commandCallable = function ($url) use ($path, $command, $cachePath) {
@ -99,13 +115,51 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
/**
* {@inheritDoc}
*/
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{
GitUtil::cleanEnv();
$path = $this->normalizePath($path);
if (!$this->hasMetadataRepository($path)) {
throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
}
$cachePath = $this->config->get('cache-vcs-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $url).'/';
$ref = $target->getSourceReference();
$flag = Platform::isWindows() ? '/D ' : '';
if (!empty($this->cachedPackages[$target->getId()][$ref])) {
$msg = "Checking out ".$this->getShortHash($ref).' from cache';
$command = 'git rev-parse --quiet --verify %ref% || (git remote set-url composer %cachePath% && git fetch composer && git fetch --tags composer); git remote set-url composer %url%';
} else {
$msg = "Checking out ".$this->getShortHash($ref);
$command = 'git remote set-url composer %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)';
if (getenv('COMPOSER_DISABLE_NETWORK')) {
throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
}
}
$this->io->writeError($msg);
$commandCallable = function ($url) use ($ref, $command, $cachePath) {
return str_replace(
array('%url%', '%ref%', '%cachePath%'),
array(
ProcessExecutor::escape($url),
ProcessExecutor::escape($ref.'^{commit}'),
ProcessExecutor::escape($cachePath),
),
$command
);
};
$this->gitUtil->runCommand($commandCallable, $url, $path);
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
if ($target->getDistReference() === $target->getSourceReference()) {
$target->setDistReference($newRef);
}
$target->setSourceReference($newRef);
}
$updateOriginUrl = false;
if (
0 === $this->process->execute('git remote -v', $output, $path)
@ -116,23 +170,6 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
$updateOriginUrl = true;
}
}
$ref = $target->getSourceReference();
$this->io->writeError(" Checking out ".$this->getShortHash($ref));
$command = 'git remote set-url composer %s && git rev-parse --quiet --verify %s || (git fetch composer && git fetch --tags composer)';
$commandCallable = function ($url) use ($command, $ref) {
return sprintf($command, ProcessExecutor::escape($url), ProcessExecutor::escape($ref.'^{commit}'));
};
$this->gitUtil->runCommand($commandCallable, $url, $path);
if ($newRef = $this->updateToCommit($path, $ref, $target->getPrettyVersion(), $target->getReleaseDate())) {
if ($target->getDistReference() === $target->getSourceReference()) {
$target->setDistReference($newRef);
}
$target->setSourceReference($newRef);
}
if ($updateOriginUrl) {
$this->updateOriginUrl($path, $target->getSourceUrl());
}

View File

@ -24,7 +24,15 @@ class HgDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doInstall(PackageInterface $package, $path, $url)
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
protected function doInstall(PackageInterface $package, $path, $url)
{
$hgUtils = new HgUtils($this->io, $this->config, $this->process);
@ -44,7 +52,7 @@ class HgDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{
$hgUtils = new HgUtils($this->io, $this->config, $this->process);

View File

@ -37,7 +37,7 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
/**
* {@inheritdoc}
*/
public function download(PackageInterface $package, $path, $output = true)
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
{
$url = $package->getDistUrl();
$realUrl = realpath($url);
@ -49,6 +49,10 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
));
}
if (realpath($path) === $realUrl) {
return;
}
if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) {
// IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours.
//
@ -71,6 +75,20 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
$url = $package->getDistUrl();
$realUrl = realpath($url);
if (realpath($path) === $realUrl) {
if ($output) {
$this->io->writeError(sprintf(
' - Installing <info>%s</info> (<comment>%s</comment>): Source already present',
$package->getName(),
$package->getFullPrettyVersion()
));
} else {
$this->io->writeError('Source already present', false);
}
return;
}
// Get the transport options with default values
$transportOptions = $package->getTransportOptions() + array('symlink' => null);
@ -147,7 +165,9 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
$fileSystem->mirror($realUrl, $path, $iterator);
}
$this->io->writeError('');
if ($output) {
$this->io->writeError('');
}
}
/**
@ -155,6 +175,16 @@ class PathDownloader extends FileDownloader implements VcsCapableDownloaderInter
*/
public function remove(PackageInterface $package, $path, $output = true)
{
$realUrl = realpath($package->getDistUrl());
if ($path === $realUrl) {
if ($output) {
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>), source is still present in $path");
}
return;
}
/**
* For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
* inadvertently locks the file the removal will fail, but it would fall back to recursive delete which

View File

@ -24,6 +24,14 @@ class PerforceDownloader extends VcsDownloader
/** @var Perforce */
protected $perforce;
/**
* {@inheritDoc}
*/
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
@ -76,7 +84,7 @@ class PerforceDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{
$this->doInstall($target, $path, $url);
}
@ -87,8 +95,6 @@ class PerforceDownloader extends VcsDownloader
public function getLocalChanges(PackageInterface $package, $path)
{
$this->io->writeError('Perforce driver does not check for local changes before overriding', true);
return null;
}
/**

View File

@ -28,7 +28,15 @@ class SvnDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doInstall(PackageInterface $package, $path, $url)
protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
protected function doInstall(PackageInterface $package, $path, $url)
{
SvnUtil::cleanEnv();
$ref = $package->getSourceReference();
@ -48,7 +56,7 @@ class SvnDownloader extends VcsDownloader
/**
* {@inheritDoc}
*/
public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
{
SvnUtil::cleanEnv();
$ref = $target->getSourceReference();

View File

@ -20,6 +20,7 @@ use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
@ -54,9 +55,57 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, $path)
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null)
{
// noop for now, ideally we would do a git fetch already here, or make sure the cached git repo is synced, etc.
if (!$package->getSourceReference()) {
throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
}
$urls = $this->prepareUrls($package->getSourceUrls());
while ($url = array_shift($urls)) {
try {
return $this->doDownload($package, $path, $url, $prevPackage);
} catch (\Exception $e) {
// rethrow phpunit exceptions to avoid hard to debug bug failures
if ($e instanceof \PHPUnit_Framework_Exception) {
throw $e;
}
if ($this->io->isDebug()) {
$this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
} elseif (count($urls)) {
$this->io->writeError(' Failed, trying the next URL');
}
if (!count($urls)) {
throw $e;
}
}
}
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
{
if ($type === 'update') {
$this->cleanChanges($prevPackage, $path, true);
} elseif ($type === 'install') {
$this->filesystem->emptyDirectory($path);
} elseif ($type === 'uninstall') {
$this->cleanChanges($package, $path, false);
}
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, $path, PackageInterface $prevPackage = null)
{
if ($type === 'update') {
// TODO keep track of whether prepare was called for this package
$this->reapplyChanges($path);
}
}
/**
@ -69,32 +118,10 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
}
$this->io->writeError(" - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
$this->filesystem->emptyDirectory($path);
$urls = $package->getSourceUrls();
$urls = $this->prepareUrls($package->getSourceUrls());
while ($url = array_shift($urls)) {
try {
if (Filesystem::isLocalPath($url)) {
// realpath() below will not understand
// url that starts with "file://"
$needle = 'file://';
$isFileProtocol = false;
if (0 === strpos($url, $needle)) {
$url = substr($url, strlen($needle));
$isFileProtocol = true;
}
// realpath() below will not understand %20 spaces etc.
if (false !== strpos($url, '%')) {
$url = rawurldecode($url);
}
$url = realpath($url);
if ($isFileProtocol) {
$url = $needle . $url;
}
}
$this->doInstall($package, $path, $url);
break;
} catch (\Exception $e) {
@ -141,15 +168,11 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
$actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
$this->io->writeError(" - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);
$this->cleanChanges($initial, $path, true);
$urls = $target->getSourceUrls();
$urls = $this->prepareUrls($target->getSourceUrls());
$exception = null;
while ($url = array_shift($urls)) {
try {
if (Filesystem::isLocalPath($url)) {
$url = realpath($url);
}
$this->doUpdate($initial, $target, $path, $url);
$exception = null;
@ -167,8 +190,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
}
}
$this->reapplyChanges($path);
// print the commit logs if in verbose mode and VCS metadata is present
// because in case of missing metadata code would trigger another exception
if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
@ -204,7 +225,6 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
public function remove(PackageInterface $package, $path)
{
$this->io->writeError(" - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
$this->cleanChanges($package, $path, false);
if (!$this->filesystem->removeDirectory($path)) {
throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
}
@ -243,7 +263,7 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
}
/**
* Guarantee that no changes have been made to the local copy
* Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not)
*
* @param string $path
* @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
@ -252,12 +272,26 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
{
}
/**
* Downloads data needed to run an install/update later
*
* @param PackageInterface $package package instance
* @param string $path download path
* @param string $url package url
* @param PackageInterface|null $prevPackage previous package (in case of an update)
*
* @return PromiseInterface|null
*/
abstract protected function doDownload(PackageInterface $package, $path, $url, PackageInterface $prevPackage = null);
/**
* Downloads specific package into specific folder.
*
* @param PackageInterface $package package instance
* @param string $path download path
* @param string $url package url
*
* @return PromiseInterface|null
*/
abstract protected function doInstall(PackageInterface $package, $path, $url);
@ -268,6 +302,8 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* @param PackageInterface $target updated package
* @param string $path download path
* @param string $url package url
*
* @return PromiseInterface|null
*/
abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url);
@ -289,4 +325,33 @@ abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterfa
* @return bool
*/
abstract protected function hasMetadataRepository($path);
private function prepareUrls(array $urls)
{
foreach ($urls as $index => $url) {
if (Filesystem::isLocalPath($url)) {
// realpath() below will not understand
// url that starts with "file://"
$fileProtocol = 'file://';
$isFileProtocol = false;
if (0 === strpos($url, $fileProtocol)) {
$url = substr($url, strlen($fileProtocol));
$isFileProtocol = true;
}
// realpath() below will not understand %20 spaces etc.
if (false !== strpos($url, '%')) {
$url = rawurldecode($url);
}
$urls[$index] = realpath($url);
if ($isFileProtocol) {
$urls[$index] = $fileProtocol . $urls[$index];
}
}
}
return $urls;
}
}

View File

@ -47,7 +47,7 @@ class ZipDownloader extends ArchiveDownloader
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, $path, $output = true)
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
{
if (null === self::$hasSystemUnzip) {
$finder = new ExecutableFinder;
@ -76,7 +76,7 @@ class ZipDownloader extends ArchiveDownloader
}
}
return parent::download($package, $path, $output);
return parent::download($package, $path, $prevPackage, $output);
}
/**

View File

@ -201,7 +201,9 @@ class EventDispatcher
try {
/** @var InstallerEvent $event */
$return = $this->dispatch($scriptName, new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags));
$scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
$scriptEvent->setOriginatingEvent($event);
$return = $this->dispatch($scriptName, $scriptEvent);
} catch (ScriptExecutionException $e) {
$this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
throw $e;

View File

@ -392,7 +392,7 @@ class Factory
? substr($composerFile, 0, -4).'lock'
: $composerFile . '.lock';
$locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
$locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $im, file_get_contents($composerFile));
$composer->setLocker($locker);
}

View File

@ -28,6 +28,14 @@ abstract class BaseIO implements IOInterface
return $this->authentications;
}
/**
* {@inheritDoc}
*/
public function resetAuthentications()
{
$this->authentications = array();
}
/**
* {@inheritDoc}
*/

View File

@ -632,7 +632,7 @@ class Installer
$this->installationManager->execute($localRepo, $operation);
if ($this->executeOperations) {
$localRepo->write();
$localRepo->write($this->devMode, $this->installationManager);
}
$event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType);

View File

@ -177,11 +177,52 @@ class InstallationManager
$promise = $installer->download($target, $operation->getInitialPackage());
}
if (isset($promise)) {
if (!empty($promise)) {
$this->loop->wait(array($promise));
}
$this->$method($repo, $operation);
$e = null;
try {
if ($method === 'install' || $method === 'uninstall') {
$package = $operation->getPackage();
$installer = $this->getInstaller($package->getType());
$promise = $installer->prepare($method, $package);
} elseif ($method === 'update') {
$target = $operation->getTargetPackage();
$targetType = $target->getType();
$installer = $this->getInstaller($targetType);
$promise = $installer->prepare('update', $target, $operation->getInitialPackage());
}
if (!empty($promise)) {
$this->loop->wait(array($promise));
}
$promise = $this->$method($repo, $operation);
if (!empty($promise)) {
$this->loop->wait(array($promise));
}
} catch (\Exception $e) {
}
if ($method === 'install' || $method === 'uninstall') {
$package = $operation->getPackage();
$installer = $this->getInstaller($package->getType());
$promise = $installer->cleanup($method, $package);
} elseif ($method === 'update') {
$target = $operation->getTargetPackage();
$targetType = $target->getType();
$installer = $this->getInstaller($targetType);
$promise = $installer->cleanup('update', $target, $operation->getInitialPackage());
}
if (!empty($promise)) {
$this->loop->wait(array($promise));
}
if ($e) {
throw $e;
}
}
/**
@ -194,8 +235,10 @@ class InstallationManager
{
$package = $operation->getPackage();
$installer = $this->getInstaller($package->getType());
$installer->install($repo, $package);
$promise = $installer->install($repo, $package);
$this->markForNotification($package);
return $promise;
}
/**
@ -214,13 +257,15 @@ class InstallationManager
if ($initialType === $targetType) {
$installer = $this->getInstaller($initialType);
$installer->update($repo, $initial, $target);
$promise = $installer->update($repo, $initial, $target);
$this->markForNotification($target);
} else {
$this->getInstaller($initialType)->uninstall($repo, $initial);
$installer = $this->getInstaller($targetType);
$installer->install($repo, $target);
$promise = $installer->install($repo, $target);
}
return $promise;
}
/**
@ -233,7 +278,8 @@ class InstallationManager
{
$package = $operation->getPackage();
$installer = $this->getInstaller($package->getType());
$installer->uninstall($repo, $package);
return $installer->uninstall($repo, $package);
}
/**

View File

@ -46,26 +46,43 @@ interface InstallerInterface
/**
* Downloads the files needed to later install the given package.
*
* @param PackageInterface $package package instance
* @param PackageInterface $prevPackage previous package instance in case of an update
* @param PackageInterface $package package instance
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface|null
*/
public function download(PackageInterface $package, PackageInterface $prevPackage = null);
/**
* Do anything that needs to be done between all downloads have been completed and the actual operation is executed
*
* All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
* for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
* user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
* be undone as much as possible.
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface|null
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null);
/**
* Installs specific package.
*
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $package package instance
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $package package instance
* @return PromiseInterface|null
*/
public function install(InstalledRepositoryInterface $repo, PackageInterface $package);
/**
* Updates specific package.
*
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $initial already installed package version
* @param PackageInterface $target updated version
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $initial already installed package version
* @param PackageInterface $target updated version
* @return PromiseInterface|null
*
* @throws InvalidArgumentException if $initial package is not installed
*/
@ -74,11 +91,26 @@ interface InstallerInterface
/**
* Uninstalls specific package.
*
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $package package instance
* @param InstalledRepositoryInterface $repo repository in which to check
* @param PackageInterface $package package instance
* @return PromiseInterface|null
*/
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);
/**
* Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
*
* Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
* all installers a change to cleanup things they did previously, so you need to keep track of changes
* applied in the installer/downloader themselves.
*
* @param string $type one of install/update/uninstall
* @param PackageInterface $package package instance
* @param PackageInterface $prevPackage previous package instance in case of an update
* @return PromiseInterface|null
*/
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null);
/**
* Returns the installation path of a package
*

View File

@ -85,6 +85,9 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
}
/**
* {@inheritDoc}
*/
public function download(PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->initializeVendorDir();
@ -93,6 +96,28 @@ class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
return $this->downloadManager->download($package, $downloadPath, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->initializeVendorDir();
$downloadPath = $this->getInstallPath($package);
return $this->downloadManager->prepare($type, $package, $downloadPath, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->initializeVendorDir();
$downloadPath = $this->getInstallPath($package);
return $this->downloadManager->cleanup($type, $package, $downloadPath, $prevPackage);
}
/**
* {@inheritDoc}
*/

View File

@ -55,6 +55,22 @@ class MetapackageInstaller implements InstallerInterface
// noop
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
// noop
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
// noop
}
/**
* {@inheritDoc}
*/

View File

@ -47,6 +47,20 @@ class NoopInstaller implements InstallerInterface
{
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
}
/**
* {@inheritDoc}
*/

View File

@ -70,7 +70,7 @@ class PluginInstaller extends LibraryInstaller
$this->composer->getPluginManager()->registerPackage($package, true);
} catch (\Exception $e) {
// Rollback installation
$this->io->writeError('Plugin installation failed, rolling back');
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
parent::uninstall($repo, $package);
throw $e;
}
@ -81,12 +81,22 @@ class PluginInstaller extends LibraryInstaller
*/
public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
{
$extra = $target->getExtra();
if (empty($extra['class'])) {
throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
}
parent::update($repo, $initial, $target);
$this->composer->getPluginManager()->registerPackage($target, true);
try {
$this->composer->getPluginManager()->deactivatePackage($initial, true);
$this->composer->getPluginManager()->registerPackage($target, true);
} catch (\Exception $e) {
// Rollback installation
$this->io->writeError('Plugin initialization failed, uninstalling plugin');
parent::uninstall($repo, $target);
throw $e;
}
}
public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
{
$this->composer->getPluginManager()->uninstallPackage($package, true);
parent::uninstall($repo, $package);
}
}

View File

@ -71,6 +71,22 @@ class ProjectInstaller implements InstallerInterface
return $this->downloadManager->download($package, $installPath, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function prepare($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage);
}
/**
* {@inheritDoc}
*/
public function cleanup($type, PackageInterface $package, PackageInterface $prevPackage = null)
{
$this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage);
}
/**
* {@inheritDoc}
*/

View File

@ -326,9 +326,10 @@ class JsonManipulator
}
// try and find a match for the subkey
if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i', $children)) {
$keyRegex = str_replace('/', '\\\\?/', preg_quote($name));
if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) {
// find best match for the value of "name"
if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x', $children, $matches)) {
if (preg_match_all('{'.self::$DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) {
$bestMatch = '';
foreach ($matches[0] as $match) {
if (strlen($bestMatch) < strlen($match)) {

View File

@ -33,8 +33,6 @@ class Locker
{
/** @var JsonFile */
private $lockFile;
/** @var RepositoryManager */
private $repositoryManager;
/** @var InstallationManager */
private $installationManager;
/** @var string */
@ -55,14 +53,12 @@ class Locker
*
* @param IOInterface $io
* @param JsonFile $lockFile lockfile loader
* @param RepositoryManager $repositoryManager repository manager instance
* @param InstallationManager $installationManager installation manager instance
* @param string $composerFileContents The contents of the composer file
*/
public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents)
public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, $composerFileContents)
{
$this->lockFile = $lockFile;
$this->repositoryManager = $repositoryManager;
$this->installationManager = $installationManager;
$this->hash = md5($composerFileContents);
$this->contentHash = self::getContentHash($composerFileContents);

View File

@ -36,4 +36,22 @@ interface PluginInterface
* @param IOInterface $io
*/
public function activate(Composer $composer, IOInterface $io);
/**
* Remove any hooks from Composer
*
* @param Composer $composer
* @param IOInterface $io
*/
public function deactivate(Composer $composer, IOInterface $io);
/**
* Prepare the plugin to be uninstalled
*
* This will be called after deactivate
*
* @param Composer $composer
* @param IOInterface $io
*/
public function uninstall(Composer $composer, IOInterface $io);
}

View File

@ -145,7 +145,7 @@ class PluginManager
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
if (in_array($package->getName(), $this->registeredPlugins)) {
if (isset($this->registeredPlugins[$package->getName()])) {
return;
}
@ -201,16 +201,82 @@ class PluginManager
if ($oldInstallerPlugin) {
$installer = new $class($this->io, $this->composer);
$this->composer->getInstallationManager()->addInstaller($installer);
$this->registeredPlugins[$package->getName()] = $installer;
} elseif (class_exists($class)) {
$plugin = new $class();
$this->addPlugin($plugin);
$this->registeredPlugins[] = $package->getName();
$this->registeredPlugins[$package->getName()] = $plugin;
} elseif ($failOnMissingClasses) {
throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
}
}
}
/**
* Deactivates a plugin package
*
* If it's of type composer-installer it is unregistered from the installers
* instead for BC
*
* @param PackageInterface $package
*
* @throws \UnexpectedValueException
*/
public function deactivatePackage(PackageInterface $package)
{
if ($this->disablePlugins) {
return;
}
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
if (!isset($this->registeredPlugins[$package->getName()])) {
return;
}
if ($oldInstallerPlugin) {
$installer = $this->registeredPlugins[$package->getName()];
unset($this->registeredPlugins[$package->getName()]);
$this->composer->getInstallationManager()->removeInstaller($installer);
} else {
$plugin = $this->registeredPlugins[$package->getName()];
unset($this->registeredPlugins[$package->getName()]);
$this->removePlugin($plugin);
}
}
/**
* Uninstall a plugin package
*
* If it's of type composer-installer it is unregistered from the installers
* instead for BC
*
* @param PackageInterface $package
*
* @throws \UnexpectedValueException
*/
public function uninstallPackage(PackageInterface $package)
{
if ($this->disablePlugins) {
return;
}
$oldInstallerPlugin = ($package->getType() === 'composer-installer');
if (!isset($this->registeredPlugins[$package->getName()])) {
return;
}
if ($oldInstallerPlugin) {
$this->deactivatePackage($package);
} else {
$plugin = $this->registeredPlugins[$package->getName()];
unset($this->registeredPlugins[$package->getName()]);
$this->removePlugin($plugin);
$this->uninstallPlugin($plugin);
}
}
/**
* Returns the version of the internal composer-plugin-api package.
*
@ -241,6 +307,44 @@ class PluginManager
}
}
/**
* Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance
*
* Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer
* programmatically and want to deregister a plugin class directly this is a valid way
* to do it.
*
* @param PluginInterface $plugin plugin instance
*/
public function removePlugin(PluginInterface $plugin)
{
$index = array_search($plugin, $this->plugins, true);
if ($index === false) {
return;
}
$this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG);
unset($this->plugins[$index]);
$plugin->deactivate($this->composer, $this->io);
$this->composer->getEventDispatcher()->removeListener($plugin);
}
/**
* Notifies a plugin it is being uninstalled and should clean up
*
* Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer
* programmatically and want to deregister a plugin class directly this is a valid way
* to do it.
*
* @param PluginInterface $plugin plugin instance
*/
public function uninstallPlugin(PluginInterface $plugin)
{
$this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG);
$plugin->uninstall($this->composer, $this->io);
}
/**
* Load all plugins and installers from a repository
*

View File

@ -16,6 +16,7 @@ use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Zip;
/**
* @author Serge Smertin <serg.smertin@gmail.com>
@ -80,76 +81,15 @@ class ArtifactRepository extends ArrayRepository implements ConfigurableReposito
}
}
/**
* Find a file by name, returning the one that has the shortest path.
*
* @param \ZipArchive $zip
* @param string $filename
* @return bool|int
*/
private function locateFile(\ZipArchive $zip, $filename)
{
$indexOfShortestMatch = false;
$lengthOfShortestMatch = -1;
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
if (strcmp(basename($stat['name']), $filename) === 0) {
$directoryName = dirname($stat['name']);
if ($directoryName == '.') {
//if composer.json is in root directory
//it has to be the one to use.
return $i;
}
if (strpos($directoryName, '\\') !== false ||
strpos($directoryName, '/') !== false) {
//composer.json files below first directory are rejected
continue;
}
$length = strlen($stat['name']);
if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
//Check it's not a directory.
$contents = $zip->getFromIndex($i);
if ($contents !== false) {
$indexOfShortestMatch = $i;
$lengthOfShortestMatch = $length;
}
}
}
}
return $indexOfShortestMatch;
}
private function getComposerInformation(\SplFileInfo $file)
{
$zip = new \ZipArchive();
if ($zip->open($file->getPathname()) !== true) {
$json = Zip::getComposerJson($file->getPathname());
if (null === $json) {
return false;
}
if (0 == $zip->numFiles) {
$zip->close();
return false;
}
$foundFileIndex = $this->locateFile($zip, 'composer.json');
if (false === $foundFileIndex) {
$zip->close();
return false;
}
$configurationFileName = $zip->getNameIndex($foundFileIndex);
$zip->close();
$composerFile = "zip://{$file->getPathname()}#$configurationFileName";
$json = file_get_contents($composerFile);
$package = JsonFile::parseJson($json, $composerFile);
$package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
$package['dist'] = array(
'type' => 'zip',
'url' => strtr($file->getPathname(), '\\', '/'),

View File

@ -15,6 +15,8 @@ namespace Composer\Repository;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Installer\InstallationManager;
use Composer\Util\Filesystem;
/**
* Filesystem repository.
@ -49,7 +51,12 @@ class FilesystemRepository extends WritableArrayRepository
}
try {
$packages = $this->file->read();
$data = $this->file->read();
if (isset($data['packages'])) {
$packages = $data['packages'];
} else {
$packages = $data;
}
// forward compatibility for composer v2 installed.json
if (isset($packages['packages'])) {
@ -79,16 +86,21 @@ class FilesystemRepository extends WritableArrayRepository
/**
* Writes writable repository.
*/
public function write()
public function write($devMode, InstallationManager $installationManager)
{
$data = array();
$data = array('packages' => array(), 'dev' => $devMode);
$dumper = new ArrayDumper();
$fs = new Filesystem();
$repoDir = dirname($fs->normalizePath($this->file->getPath()));
foreach ($this->getCanonicalPackages() as $package) {
$data[] = $dumper->dump($package);
$pkgArray = $dumper->dump($package);
$path = $installationManager->getInstallPath($package);
$pkgArray['install-path'] = ('' !== $path && null !== $path) ? $fs->findShortestPath($repoDir, $path, true) : null;
$data['packages'][] = $pkgArray;
}
usort($data, function ($a, $b) {
usort($data['packages'], function ($a, $b) {
return strcmp($a['name'], $b['name']);
});

View File

@ -19,7 +19,6 @@ use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Util\GitHub;
use Composer\Util\Http\Response;
use Composer\Util\RemoteFilesystem;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
@ -308,6 +307,10 @@ class GitHubDriver extends VcsDriver
*/
protected function generateSshUrl()
{
if (false !== strpos($this->originUrl, ':')) {
return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
}
return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
}
@ -342,10 +345,10 @@ class GitHubDriver extends VcsDriver
$scopesIssued = array();
$scopesNeeded = array();
if ($headers = $e->getHeaders()) {
if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-OAuth-Scopes')) {
if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) {
$scopesIssued = explode(' ', $scopes);
}
if ($scopes = RemoteFilesystem::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
$scopesNeeded = explode(' ', $scopes);
}
}

View File

@ -68,9 +68,9 @@ class GitLabDriver extends VcsDriver
private $isPrivate = true;
/**
* @var int port number
* @var bool true if the origin has a port number or a path component in it
*/
protected $portNumber;
private $hasNonstandardOrigin = false;
const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';
@ -95,11 +95,10 @@ class GitLabDriver extends VcsDriver
? $match['scheme']
: (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
;
$this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts);
$this->originUrl = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);
if (!empty($match['port']) && true === is_numeric($match['port'])) {
// If it is an HTTP based URL, and it has a port
$this->portNumber = (int) $match['port'];
if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) {
$this->hasNonstandardOrigin = true;
}
$this->namespace = implode('/', $urlParts);
@ -260,10 +259,7 @@ class GitLabDriver extends VcsDriver
*/
public function getApiUrl()
{
$domainName = $this->originUrl;
$portNumber = (true === is_numeric($this->portNumber)) ? sprintf(':%s', $this->portNumber) : '';
return $this->scheme.'://'.$domainName.$portNumber.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository);
return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository);
}
/**
@ -362,6 +358,10 @@ class GitLabDriver extends VcsDriver
*/
protected function generateSshUrl()
{
if ($this->hasNonstandardOrigin) {
return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git';
}
return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git';
}
@ -464,7 +464,7 @@ class GitLabDriver extends VcsDriver
$guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
$urlParts = explode('/', $match['parts']);
if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts)) {
if (false === self::determineOrigin((array) $config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
return false;
}
@ -495,16 +495,23 @@ class GitLabDriver extends VcsDriver
* @param array $urlParts
* @return bool|string
*/
private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts)
private static function determineOrigin(array $configuredDomains, $guessedDomain, array &$urlParts, $portNumber)
{
if (in_array($guessedDomain, $configuredDomains)) {
if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
if ($portNumber) {
return $guessedDomain.':'.$portNumber;
}
return $guessedDomain;
}
if ($portNumber) {
$guessedDomain .= ':'.$portNumber;
}
while (null !== ($part = array_shift($urlParts))) {
$guessedDomain .= '/' . $part;
if (in_array($guessedDomain, $configuredDomains)) {
if (in_array($guessedDomain, $configuredDomains) || ($portNumber && in_array(preg_replace('{:\d+}', '', $guessedDomain), $configuredDomains))) {
return $guessedDomain;
}
}

View File

@ -13,6 +13,7 @@
namespace Composer\Repository;
use Composer\Package\AliasPackage;
use Composer\Installer\InstallationManager;
/**
* Writable array repository.
@ -24,7 +25,7 @@ class WritableArrayRepository extends ArrayRepository implements WritableReposit
/**
* {@inheritDoc}
*/
public function write()
public function write($devMode, InstallationManager $installationManager)
{
}

View File

@ -13,6 +13,7 @@
namespace Composer\Repository;
use Composer\Package\PackageInterface;
use Composer\Installer\InstallationManager;
/**
* Writable repository interface.
@ -23,8 +24,10 @@ interface WritableRepositoryInterface extends RepositoryInterface
{
/**
* Writes repository (f.e. to the disc).
*
* @param bool $devMode Whether dev requirements were included or not in this installation
*/
public function write();
public function write($devMode, InstallationManager $installationManager);
/**
* Adds package to the repository.

View File

@ -39,6 +39,11 @@ class Event extends BaseEvent
*/
private $devMode;
/**
* @var BaseEvent
*/
private $originatingEvent;
/**
* Constructor.
*
@ -55,6 +60,7 @@ class Event extends BaseEvent
$this->composer = $composer;
$this->io = $io;
$this->devMode = $devMode;
$this->originatingEvent = null;
}
/**
@ -86,4 +92,42 @@ class Event extends BaseEvent
{
return $this->devMode;
}
/**
* Set the originating event.
*
* @return \Composer\EventDispatcher\Event|null
*/
public function getOriginatingEvent()
{
return $this->originatingEvent;
}
/**
* Set the originating event.
*
* @param \Composer\EventDispatcher\Event $event
* @return $this
*/
public function setOriginatingEvent(BaseEvent $event)
{
$this->originatingEvent = $this->calculateOriginatingEvent($event);
return $this;
}
/**
* Returns the upper-most event in chain.
*
* @param \Composer\EventDispatcher\Event $event
* @return \Composer\EventDispatcher\Event
*/
private function calculateOriginatingEvent(BaseEvent $event)
{
if ($event instanceof Event && $event->getOriginatingEvent()) {
return $this->calculateOriginatingEvent($event->getOriginatingEvent());
}
return $event;
}
}

View File

@ -33,6 +33,7 @@ class ErrorHandler
*
* @static
* @throws \ErrorException
* @return bool
*/
public static function handle($level, $message, $file, $line)
{
@ -63,6 +64,8 @@ class ErrorHandler
}, array_slice(debug_backtrace(), 2))));
}
}
return true;
}
/**

View File

@ -224,6 +224,10 @@ class Git
public function syncMirror($url, $dir)
{
if (getenv('COMPOSER_DISABLE_NETWORK')) {
return false;
}
// update the repo if it is a valid git repository
if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
try {
@ -260,9 +264,7 @@ class Git
}
}
$this->syncMirror($url, $dir);
return false;
return $this->syncMirror($url, $dir);
}
private function isAuthenticationFailure($url, &$match)

View File

@ -57,7 +57,10 @@ class GitLab
*/
public function authorizeOAuth($originUrl)
{
if (!in_array($originUrl, $this->config->get('gitlab-domains'), true)) {
// before composer 1.9, origin URLs had no port number in them
$bcOriginUrl = preg_replace('{:\d+}', '', $originUrl);
if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) {
return false;
}
@ -77,6 +80,12 @@ class GitLab
return true;
}
if (isset($authTokens[$bcOriginUrl])) {
$this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token');
return true;
}
return false;
}

View File

@ -16,7 +16,6 @@ use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
use Composer\Util\RemoteFilesystem;
use Composer\Util\StreamContextFactory;
use Composer\Util\AuthHelper;
use Composer\Util\Url;

View File

@ -61,20 +61,7 @@ class Response
public function getHeader($name)
{
$value = null;
foreach ($this->headers as $header) {
if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) {
$value = $match[1];
} elseif (preg_match('{^HTTP/}i', $header)) {
// TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary
//
// In case of redirects, headers contains the headers of all responses
// so we reset the flag when a new response is being parsed as we are only interested in the last response
$value = null;
}
}
return $value;
return self::findHeaderValue($this->headers, $name);
}
public function getBody()
@ -91,4 +78,27 @@ class Response
{
$this->request = $this->code = $this->headers = $this->body = null;
}
/**
* @param array $headers array of returned headers like from getLastHeaders()
* @param string $name header name (case insensitive)
* @return string|null
*/
public static function findHeaderValue(array $headers, $name)
{
$value = null;
foreach ($headers as $header) {
if (preg_match('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) {
$value = $match[1];
} elseif (preg_match('{^HTTP/}i', $header)) {
// TODO ideally redirects would be handled in CurlDownloader/RemoteFilesystem and this becomes unnecessary
//
// In case of redirects, http_response_headers contains the headers of all responses
// so we reset the flag when a new response is being parsed as we are only interested in the last response
$value = null;
}
}
return $value;
}
}

View File

@ -363,8 +363,6 @@ class Perforce
while ($line !== false) {
$line = fgets($pipe);
}
return;
}
public function windowsLogin($password)

View File

@ -17,6 +17,7 @@ use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
use Composer\Util\HttpDownloader;
use Composer\Util\Http\Response;
/**
* @author François Pluchino <francois.pluchino@opendisplay.com>
@ -143,27 +144,6 @@ class RemoteFilesystem
return $this->lastHeaders;
}
/**
* @param array $headers array of returned headers like from getLastHeaders()
* @param string $name header name (case insensitive)
* @return string|null
*/
public static function findHeaderValue(array $headers, $name)
{
$value = null;
foreach ($headers as $header) {
if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) {
$value = $match[1];
} elseif (preg_match('{^HTTP/}i', $header)) {
// In case of redirects, http_response_headers contains the headers of all responses
// so we reset the flag when a new response is being parsed as we are only interested in the last response
$value = null;
}
}
return $value;
}
/**
* @param array $headers array of returned headers like from getLastHeaders()
* @return int|null
@ -286,13 +266,15 @@ class RemoteFilesystem
$errorMessage .= "\n";
}
$errorMessage .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg);
return true;
});
try {
$result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);
if (!empty($http_response_header[0])) {
$statusCode = $this->findStatusCode($http_response_header);
if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
if ($statusCode >= 400 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') {
HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true));
}
@ -301,7 +283,7 @@ class RemoteFilesystem
}
}
$contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null;
$contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null;
if ($contentLength && Platform::strlen($result) < $contentLength) {
// alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
$e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
@ -358,8 +340,8 @@ class RemoteFilesystem
$locationHeader = null;
if (!empty($http_response_header[0])) {
$statusCode = $this->findStatusCode($http_response_header);
$contentType = $this->findHeaderValue($http_response_header, 'content-type');
$locationHeader = $this->findHeaderValue($http_response_header, 'location');
$contentType = Response::findHeaderValue($http_response_header, 'content-type');
$locationHeader = Response::findHeaderValue($http_response_header, 'location');
}
// check for bitbucket login page asking to authenticate
@ -415,7 +397,7 @@ class RemoteFilesystem
// decode gzip
if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
$contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding');
$contentEncoding = Response::findHeaderValue($http_response_header, 'content-encoding');
$decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);
if ($decode) {
@ -459,6 +441,8 @@ class RemoteFilesystem
$errorMessage .= "\n";
}
$errorMessage .= preg_replace('{^file_put_contents\(.*?\): }', '', $msg);
return true;
});
$result = (bool) file_put_contents($fileName, $result);
restore_error_handler();
@ -696,7 +680,7 @@ class RemoteFilesystem
private function handleRedirect(array $http_response_header, array $additionalOptions, $result)
{
if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) {
if ($locationHeader = Response::findHeaderValue($http_response_header, 'location')) {
if (parse_url($locationHeader, PHP_URL_SCHEME)) {
// Absolute URL; e.g. https://example.com/composer
$targetUrl = $locationHeader;

View File

@ -19,8 +19,6 @@ use Composer\CaBundle\CaBundle;
*/
final class TlsHelper
{
private static $useOpensslParse;
/**
* Match hostname against a certificate.
*

View File

@ -70,6 +70,9 @@ class Url
}
$origin = (string) parse_url($url, PHP_URL_HOST);
if ($port = parse_url($url, PHP_URL_PORT)) {
$origin .= ':'.$port;
}
if (strpos($origin, '.github.com') === (strlen($origin) - 11)) {
return 'github.com';

108
src/Composer/Util/Zip.php Normal file
View File

@ -0,0 +1,108 @@
<?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\Util;
/**
* @author Andreas Schempp <andreas.schempp@terminal42.ch>
*/
class Zip
{
/**
* Gets content of the root composer.json inside a ZIP archive.
*
* @param string $pathToZip
* @param string $filename
*
* @return string|null
*/
public static function getComposerJson($pathToZip)
{
if (!extension_loaded('zip')) {
throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
}
$zip = new \ZipArchive();
if ($zip->open($pathToZip) !== true) {
return null;
}
if (0 == $zip->numFiles) {
$zip->close();
return null;
}
$foundFileIndex = self::locateFile($zip, 'composer.json');
if (false === $foundFileIndex) {
$zip->close();
return null;
}
$content = null;
$configurationFileName = $zip->getNameIndex($foundFileIndex);
$stream = $zip->getStream($configurationFileName);
if (false !== $stream) {
$content = stream_get_contents($stream);
}
$zip->close();
return $content;
}
/**
* Find a file by name, returning the one that has the shortest path.
*
* @param \ZipArchive $zip
* @param string $filename
*
* @return bool|int
*/
private static function locateFile(\ZipArchive $zip, $filename)
{
$indexOfShortestMatch = false;
$lengthOfShortestMatch = -1;
for ($i = 0; $i < $zip->numFiles; $i++) {
$stat = $zip->statIndex($i);
if (strcmp(basename($stat['name']), $filename) === 0) {
$directoryName = dirname($stat['name']);
if ($directoryName === '.') {
//if composer.json is in root directory
//it has to be the one to use.
return $i;
}
if (strpos($directoryName, '\\') !== false ||
strpos($directoryName, '/') !== false) {
//composer.json files below first directory are rejected
continue;
}
$length = strlen($stat['name']);
if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
//Check it's not a directory.
$contents = $zip->getFromIndex($i);
if ($contents !== false) {
$indexOfShortestMatch = $i;
$lengthOfShortestMatch = $length;
}
}
}
}
return $indexOfShortestMatch;
}
}

View File

@ -162,18 +162,18 @@ class AllFunctionalTest extends TestCase
}
};
for ($i = 0, $c = count($tokens); $i < $c; $i++) {
if ('' === $tokens[$i] && null === $section) {
foreach ($tokens as $token) {
if ('' === $token && null === $section) {
continue;
}
// Handle section headers.
if (null === $section) {
$section = $tokens[$i];
$section = $token;
continue;
}
$sectionData = $tokens[$i];
$sectionData = $token;
// Allow sections to validate, or modify their section data.
switch ($section) {

View File

@ -152,11 +152,4 @@ class RuleSetTest extends TestCase
$this->assertContains('JOB : Install command rule (install foo 2.1)', $ruleSet->getPrettyString($pool));
}
private function getRuleMock()
{
return $this->getMockBuilder('Composer\DependencyResolver\Rule')
->disableOriginalConstructor()
->getMock();
}
}

View File

@ -48,7 +48,7 @@ class FossilDownloaderTest extends TestCase
/**
* @expectedException \InvalidArgumentException
*/
public function testDownloadForPackageWithoutSourceReference()
public function testInstallForPackageWithoutSourceReference()
{
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->once())
@ -59,7 +59,7 @@ class FossilDownloaderTest extends TestCase
$downloader->install($packageMock, '/path');
}
public function testDownload()
public function testInstall()
{
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@ -104,7 +104,9 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(null));
$downloader = $this->getDownloaderMock();
$downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock);
$downloader->update($initialPackageMock, $sourcePackageMock, '/path');
$downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock);
}
public function testUpdate()
@ -140,7 +142,9 @@ class FossilDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
}
public function testRemove()

View File

@ -17,6 +17,7 @@ use Composer\Config;
use Composer\Test\TestCase;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Prophecy\Argument;
class GitDownloaderTest extends TestCase
{
@ -79,7 +80,10 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(null));
$downloader = $this->getDownloaderMock();
$downloader->download($packageMock, '/path');
$downloader->prepare('install', $packageMock, '/path');
$downloader->install($packageMock, '/path');
$downloader->cleanup('install', $packageMock, '/path');
}
public function testDownload()
@ -130,7 +134,10 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->prepare('install', $packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
$downloader->cleanup('install', $packageMock, 'composerPath');
}
public function testDownloadWithCache()
@ -195,7 +202,10 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->prepare('install', $packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
$downloader->cleanup('install', $packageMock, 'composerPath');
@rmdir($cachePath);
}
@ -265,7 +275,10 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->prepare('install', $packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
$downloader->cleanup('install', $packageMock, 'composerPath');
}
public function pushUrlProvider()
@ -329,12 +342,12 @@ class GitDownloaderTest extends TestCase
$config->merge(array('config' => array('github-protocols' => $protocols)));
$downloader = $this->getDownloaderMock(null, $config, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->prepare('install', $packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
$downloader->cleanup('install', $packageMock, 'composerPath');
}
/**
* @expectedException \RuntimeException
*/
public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
{
$expectedGitCommand = $this->winCompat("git clone --no-checkout 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git remote add composer 'https://example.com/composer/composer' && git fetch composer");
@ -359,8 +372,20 @@ class GitDownloaderTest extends TestCase
->with($this->equalTo($expectedGitCommand))
->will($this->returnValue(1));
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->install($packageMock, 'composerPath');
// not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe
try {
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->download($packageMock, 'composerPath');
$downloader->prepare('install', $packageMock, 'composerPath');
$downloader->install($packageMock, 'composerPath');
$downloader->cleanup('install', $packageMock, 'composerPath');
$this->fail('This test should throw');
} catch (\RuntimeException $e) {
if ('RuntimeException' !== get_class($e)) {
throw $e;
}
$this->assertEquals('RuntimeException', get_class($e));
}
}
/**
@ -375,7 +400,10 @@ class GitDownloaderTest extends TestCase
->will($this->returnValue(null));
$downloader = $this->getDownloaderMock();
$downloader->download($sourcePackageMock, '/path', $initialPackageMock);
$downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock);
$downloader->update($initialPackageMock, $sourcePackageMock, '/path');
$downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock);
}
public function testUpdate()
@ -392,39 +420,22 @@ class GitDownloaderTest extends TestCase
$packageMock->expects($this->any())
->method('getVersion')
->will($this->returnValue('1.0.0.0'));
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
$processExecutor->expects($this->at(0))
->method('execute')
->with($this->equalTo($this->winCompat("git show-ref --head -d")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(2))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(4))
->method('execute')
->with($this->equalTo($this->winCompat($expectedGitUpdateCommand)), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
$processExecutor->expects($this->at(5))
->method('execute')
->with($this->equalTo('git branch -r'))
->will($this->returnValue(0));
$processExecutor->expects($this->at(6))
->method('execute')
->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
$process = $this->prophesize('Composer\Util\ProcessExecutor');
$process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0);
$process->execute($expectedGitUpdateCommand, null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled();
$process->execute($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled();
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader = $this->getDownloaderMock(null, new Config(), $process->reveal());
$downloader->download($packageMock, $this->workingDir, $packageMock);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
}
public function testUpdateWithNewRepoUrl()
@ -444,27 +455,20 @@ class GitDownloaderTest extends TestCase
$packageMock->expects($this->any())
->method('getVersion')
->will($this->returnValue('1.0.0.0'));
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
$processExecutor->expects($this->at(0))
->method('execute')
->with($this->equalTo($this->winCompat("git show-ref --head -d")))
->with($this->equalTo($this->winCompat("git --version")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
->with($this->equalTo($this->winCompat("git show-ref --head -d")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(2))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnCallback(function ($cmd, &$output, $cwd) {
$output = 'origin https://github.com/old/url (fetch)
origin https://github.com/old/url (push)
composer https://github.com/old/url (fetch)
composer https://github.com/old/url (push)
';
return 0;
}));
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
@ -482,26 +486,41 @@ composer https://github.com/old/url (push)
->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
$processExecutor->expects($this->at(7))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnCallback(function ($cmd, &$output, $cwd) {
$output = 'origin https://github.com/old/url (fetch)
origin https://github.com/old/url (push)
composer https://github.com/old/url (fetch)
composer https://github.com/old/url (push)
';
return 0;
}));
$processExecutor->expects($this->at(8))
->method('execute')
->with($this->equalTo($this->winCompat("git remote set-url origin 'https://github.com/composer/composer'")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
$processExecutor->expects($this->at(8))
$processExecutor->expects($this->at(9))
->method('execute')
->with($this->equalTo($this->winCompat("git remote set-url --push origin 'git@github.com:composer/composer.git'")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader->download($packageMock, $this->workingDir, $packageMock);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
}
/**
* @group failing
* @expectedException \RuntimeException
*/
public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
{
$expectedGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
$expectedGitUpdateCommand2 = $this->winCompat("git remote set-url composer 'git@github.com:composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
$packageMock->expects($this->any())
@ -513,36 +532,38 @@ composer https://github.com/old/url (push)
$packageMock->expects($this->any())
->method('getVersion')
->will($this->returnValue('1.0.0.0'));
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
$processExecutor->expects($this->at(0))
->method('execute')
->with($this->equalTo($this->winCompat("git show-ref --head -d")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(2))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(4))
->method('execute')
->with($this->equalTo($expectedGitUpdateCommand))
->will($this->returnValue(1));
$process = $this->prophesize('Composer\Util\ProcessExecutor');
$process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0);
$process->execute($expectedGitUpdateCommand, null, $this->winCompat($this->workingDir))->willReturn(1)->shouldBeCalled();
$process->execute($expectedGitUpdateCommand2, null, $this->winCompat($this->workingDir))->willReturn(1)->shouldBeCalled();
$process->getErrorOutput()->willReturn('');
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader->update($packageMock, $packageMock, $this->workingDir);
// not using PHPUnit's expected exception because Prophecy exceptions extend from RuntimeException too so it is not safe
try {
$downloader = $this->getDownloaderMock(null, new Config(), $process->reveal());
$downloader->download($packageMock, $this->workingDir, $packageMock);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
$this->fail('This test should throw');
} catch (\RuntimeException $e) {
if ('RuntimeException' !== get_class($e)) {
throw $e;
}
$this->assertEquals('RuntimeException', get_class($e));
}
}
public function testUpdateDoesntThrowsRuntimeExceptionIfGitCommandFailsAtFirstButIsAbleToRecover()
{
$expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
$expectedFirstGitUpdateCommand = $this->winCompat("git remote set-url composer '".(Platform::isWindows() ? 'C:\\' : '/')."' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
$expectedSecondGitUpdateCommand = $this->winCompat("git remote set-url composer 'https://github.com/composer/composer' && git rev-parse --quiet --verify 'ref^{commit}' || (git fetch composer && git fetch --tags composer)");
$packageMock = $this->getMockBuilder('Composer\Package\PackageInterface')->getMock();
@ -554,52 +575,24 @@ composer https://github.com/old/url (push)
->will($this->returnValue('1.0.0.0'));
$packageMock->expects($this->any())
->method('getSourceUrls')
->will($this->returnValue(array('/foo/bar', 'https://github.com/composer/composer')));
$processExecutor = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
$processExecutor->expects($this->at(0))
->method('execute')
->with($this->equalTo($this->winCompat("git show-ref --head -d")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(1))
->method('execute')
->with($this->equalTo($this->winCompat("git status --porcelain --untracked-files=no")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(2))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(3))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(4))
->method('execute')
->with($this->equalTo($expectedFirstGitUpdateCommand))
->will($this->returnValue(1));
$processExecutor->expects($this->at(6))
->method('execute')
->with($this->equalTo($this->winCompat("git --version")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(7))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(8))
->method('execute')
->with($this->equalTo($this->winCompat("git remote -v")))
->will($this->returnValue(0));
$processExecutor->expects($this->at(9))
->method('execute')
->with($this->equalTo($expectedSecondGitUpdateCommand))
->will($this->returnValue(0));
$processExecutor->expects($this->at(11))
->method('execute')
->with($this->equalTo($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --")), $this->equalTo(null), $this->equalTo($this->winCompat($this->workingDir)))
->will($this->returnValue(0));
->will($this->returnValue(array(Platform::isWindows() ? 'C:\\' : '/', 'https://github.com/composer/composer')));
$process = $this->prophesize('Composer\Util\ProcessExecutor');
$process->execute($this->winCompat('git --version'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git show-ref --head -d'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git status --porcelain --untracked-files=no'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git remote -v'), Argument::cetera())->willReturn(0);
$process->execute($this->winCompat('git branch -r'), Argument::cetera())->willReturn(0);
$process->execute($expectedFirstGitUpdateCommand, Argument::cetera())->willReturn(1)->shouldBeCalled();
$process->execute($expectedSecondGitUpdateCommand, Argument::cetera())->willReturn(0)->shouldBeCalled();
$process->execute($this->winCompat("git checkout 'ref' -- && git reset --hard 'ref' --"), null, $this->winCompat($this->workingDir))->willReturn(0)->shouldBeCalled();
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock(null, new Config(), $processExecutor);
$downloader = $this->getDownloaderMock(null, new Config(), $process->reveal());
$downloader->download($packageMock, $this->workingDir, $packageMock);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
}
public function testDowngradeShowsAppropriateMessage()
@ -644,7 +637,10 @@ composer https://github.com/old/url (push)
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock($ioMock, null, $processExecutor);
$downloader->download($newPackage, $this->workingDir, $oldPackage);
$downloader->prepare('update', $newPackage, $this->workingDir, $oldPackage);
$downloader->update($oldPackage, $newPackage, $this->workingDir);
$downloader->cleanup('update', $newPackage, $this->workingDir, $oldPackage);
}
public function testNotUsingDowngradingWithReferences()
@ -679,11 +675,14 @@ composer https://github.com/old/url (push)
$ioMock = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$ioMock->expects($this->at(0))
->method('writeError')
->with($this->stringContains('updating'));
->with($this->stringContains('Updating'));
$this->fs->ensureDirectoryExists($this->workingDir.'/.git');
$downloader = $this->getDownloaderMock($ioMock, null, $processExecutor);
$downloader->download($newPackage, $this->workingDir, $oldPackage);
$downloader->prepare('update', $newPackage, $this->workingDir, $oldPackage);
$downloader->update($oldPackage, $newPackage, $this->workingDir);
$downloader->cleanup('update', $newPackage, $this->workingDir, $oldPackage);
}
public function testRemove()
@ -703,7 +702,9 @@ composer https://github.com/old/url (push)
->will($this->returnValue(true));
$downloader = $this->getDownloaderMock(null, null, $processExecutor, $filesystem);
$downloader->prepare('uninstall', $packageMock, 'composerPath');
$downloader->remove($packageMock, 'composerPath');
$downloader->cleanup('uninstall', $packageMock, 'composerPath');
}
public function testGetInstallationSource()

View File

@ -98,7 +98,9 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(null));
$downloader = $this->getDownloaderMock();
$downloader->prepare('update', $sourcePackageMock, '/path', $initialPackageMock);
$downloader->update($initialPackageMock, $sourcePackageMock, '/path');
$downloader->cleanup('update', $sourcePackageMock, '/path', $initialPackageMock);
}
public function testUpdate()
@ -129,7 +131,9 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(0));
$downloader = $this->getDownloaderMock(null, null, $processExecutor);
$downloader->prepare('update', $packageMock, $this->workingDir, $packageMock);
$downloader->update($packageMock, $packageMock, $this->workingDir);
$downloader->cleanup('update', $packageMock, $this->workingDir, $packageMock);
}
public function testRemove()
@ -148,7 +152,9 @@ class HgDownloaderTest extends TestCase
->will($this->returnValue(true));
$downloader = $this->getDownloaderMock(null, null, $processExecutor, $filesystem);
$downloader->prepare('uninstall', $packageMock, 'composerPath');
$downloader->remove($packageMock, 'composerPath');
$downloader->cleanup('uninstall', $packageMock, 'composerPath');
}
public function testGetInstallationSource()

View File

@ -338,7 +338,7 @@ class ZipDownloaderTest extends TestCase
class MockedZipDownloader extends ZipDownloader
{
public function download(PackageInterface $package, $path, $output = true)
public function download(PackageInterface $package, $path, PackageInterface $prevPackage = null, $output = true)
{
return;
}

View File

@ -17,37 +17,38 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
{
"name": "a/a", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
},
{
"name": "b/b", "version": "2.0.3",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
},
{
"name": "c/c", "version": "1.0.0",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/c/newc", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/c/newc/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
},
{
"name": "d/d", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/d/newd", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/d/newd/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
},
{
"name": "e/e", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/e/newe", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/e/newe/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
},
{
"name": "f/f", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/f/newf", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/f/newf/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" },
"transport-options": { "foo": "bar2" }
},
{
"name": "g/g", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/g/newg", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/zipball/2222222222222222222222222222222222222222", "type": "zip" }
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/g/newg/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" }
}
]
}
@ -67,32 +68,34 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
{
"name": "a/a", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" }
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }
},
{
"name": "b/b", "version": "2.0.3",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" }
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }
},
{
"name": "c/c", "version": "1.0.0",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" }
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }
},
{
"name": "d/d", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" }
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" }
},
{
"name": "f/f", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" }
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"transport-options": { "foo": "bar" }
},
{
"name": "g/g", "version": "dev-master",
"source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" },
"dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" }
"dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" },
"transport-options": { "foo": "bar" }
}
]
--LOCK--
@ -101,38 +104,40 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
{
"name": "a/a", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/a/a", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/a/a/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library"
},
{
"name": "b/b", "version": "2.0.3",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/b/b", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/b/b/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library"
},
{
"name": "c/c", "version": "1.0.0",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library"
},
{
"name": "d/d", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/d", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/d/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library"
},
{
"name": "f/f", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"type": "library"
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library",
"transport-options": { "foo": "bar" }
},
{
"name": "g/g", "version": "dev-master",
"source": { "reference": "0000000000000000000000000000000000000000", "url": "https://github.com/g/g", "type": "git" },
"dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip" },
"type": "library"
"dist": { "reference": "0000000000000000000000000000000000000000", "url": "https://api.github.com/repos/g/g/zipball/0000000000000000000000000000000000000000", "type": "zip", "shasum": "oldsum" },
"type": "library",
"transport-options": { "foo": "bar" }
}
],
"packages-dev": [],
@ -150,43 +155,44 @@ g/g is dev and installed in a different ref than the #ref, so it gets updated an
{
"name": "a/a", "version": "dev-master",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/a/newa", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/zipball/2222222222222222222222222222222222222222", "type": "zip" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/a/newa/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" },
"type": "library"
},
{
"name": "b/b", "version": "2.0.3",
"source": { "reference": "2222222222222222222222222222222222222222", "url": "https://github.com/b/newb", "type": "git" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/zipball/2222222222222222222222222222222222222222", "type": "zip" },
"dist": { "reference": "2222222222222222222222222222222222222222", "url": "https://api.github.com/repos/b/newb/tarball/2222222222222222222222222222222222222222", "type": "tar", "shasum": "newsum" },
"type": "library"
},
{
"name": "c/c", "version": "1.0.0",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/c/c", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/c/c/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library"
},
{
"name": "d/d", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/d/newd", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/d/newd/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" },
"type": "library"
},
{
"name": "e/e", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/e/newe", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/e/newe/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" },
"type": "library"
},
{
"name": "f/f", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/f/f", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"type": "library"
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/f/f/zipball/1111111111111111111111111111111111111111", "type": "zip", "shasum": "oldsum" },
"type": "library",
"transport-options": { "foo": "bar" }
},
{
"name": "g/g", "version": "dev-master",
"source": { "reference": "1111111111111111111111111111111111111111", "url": "https://github.com/g/newg", "type": "git" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/zipball/1111111111111111111111111111111111111111", "type": "zip" },
"dist": { "reference": "1111111111111111111111111111111111111111", "url": "https://api.github.com/repos/g/newg/tarball/1111111111111111111111111111111111111111", "type": "tar", "shasum": "newsum" },
"type": "library"
}
],

View File

@ -246,7 +246,7 @@ class InstallerTest extends TestCase
}
$contents = json_encode($composerConfig);
$locker = new Locker($io, $lockJsonMock, $repositoryManager, $composer->getInstallationManager(), $contents);
$locker = new Locker($io, $lockJsonMock, $composer->getInstallationManager(), $contents);
$composer->setLocker($locker);
$eventDispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();

View File

@ -1448,6 +1448,22 @@ class JsonManipulatorTest extends TestCase
"repositories": {
}
}
',
),
'works on simple ones escaped slash' => array(
'{
"repositories": {
"foo\/bar": {
"bar": "baz"
}
}
}',
'foo/bar',
true,
'{
"repositories": {
}
}
',
),
'works on simple ones middle' => array(

View File

@ -13,6 +13,7 @@
namespace Composer\Test\Mock;
use Composer\Repository\InstalledFilesystemRepository;
use Composer\Installer\InstallationManager;
class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository
{
@ -20,7 +21,7 @@ class InstalledFilesystemRepositoryMock extends InstalledFilesystemRepository
{
}
public function write()
public function write($devMode, InstallationManager $installationManager)
{
}
}

View File

@ -148,7 +148,6 @@ class ArrayLoaderTest extends TestCase
{
$package = $this->loader->load($config);
$dumper = new ArrayDumper;
$expectedConfig = $config;
$expectedConfig = $this->fixConfigWhenLoadConfigIsFalse($config);
$this->assertEquals($expectedConfig, $dumper->dump($package));
}

View File

@ -24,7 +24,6 @@ class LockerTest extends TestCase
$locker = new Locker(
new NullIO,
$json,
$this->createRepositoryManagerMock(),
$this->createInstallationManagerMock(),
$this->getJsonContent()
);
@ -44,10 +43,9 @@ class LockerTest extends TestCase
public function testGetNotLockedPackages()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent());
$locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent());
$json
->expects($this->once())
@ -62,10 +60,9 @@ class LockerTest extends TestCase
public function testGetLockedPackages()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent());
$locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent());
$json
->expects($this->once())
@ -89,11 +86,10 @@ class LockerTest extends TestCase
public function testSetLockData()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$jsonContent = $this->getJsonContent() . ' ';
$locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent);
$locker = new Locker(new NullIO, $json, $inst, $jsonContent);
$package1 = $this->createPackageMock();
$package2 = $this->createPackageMock();
@ -162,10 +158,9 @@ class LockerTest extends TestCase
public function testLockBadPackages()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent());
$locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent());
$package1 = $this->createPackageMock();
$package1
@ -181,11 +176,10 @@ class LockerTest extends TestCase
public function testIsFresh()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$jsonContent = $this->getJsonContent();
$locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent);
$locker = new Locker(new NullIO, $json, $inst, $jsonContent);
$json
->expects($this->once())
@ -198,10 +192,9 @@ class LockerTest extends TestCase
public function testIsFreshFalse()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent());
$locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent());
$json
->expects($this->once())
@ -214,11 +207,10 @@ class LockerTest extends TestCase
public function testIsFreshWithContentHash()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$jsonContent = $this->getJsonContent();
$locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent);
$locker = new Locker(new NullIO, $json, $inst, $jsonContent);
$json
->expects($this->once())
@ -231,11 +223,10 @@ class LockerTest extends TestCase
public function testIsFreshWithContentHashAndNoHash()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$jsonContent = $this->getJsonContent();
$locker = new Locker(new NullIO, $json, $repo, $inst, $jsonContent);
$locker = new Locker(new NullIO, $json, $inst, $jsonContent);
$json
->expects($this->once())
@ -248,10 +239,9 @@ class LockerTest extends TestCase
public function testIsFreshFalseWithContentHash()
{
$json = $this->createJsonFileMock();
$repo = $this->createRepositoryManagerMock();
$inst = $this->createInstallationManagerMock();
$locker = new Locker(new NullIO, $json, $repo, $inst, $this->getJsonContent());
$locker = new Locker(new NullIO, $json, $inst, $this->getJsonContent());
$differentHash = md5($this->getJsonContent(array('name' => 'test2')));
@ -270,19 +260,6 @@ class LockerTest extends TestCase
->getMock();
}
private function createRepositoryManagerMock()
{
$mock = $this->getMockBuilder('Composer\Repository\RepositoryManager')
->disableOriginalConstructor()
->getMock();
$mock->expects($this->any())
->method('getLocalRepository')
->will($this->returnValue($this->getMockBuilder('Composer\Repository\ArrayRepository')->getMock()));
return $mock;
}
private function createInstallationManagerMock()
{
$mock = $this->getMockBuilder('Composer\Installer\InstallationManager')

View File

@ -12,5 +12,16 @@ class Plugin implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v1');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v1');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v1');
}
}

View File

@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v2');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v2');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v2');
}
}

View File

@ -12,5 +12,16 @@ class Plugin2 implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v3');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v3');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v3');
}
}

View File

@ -13,5 +13,16 @@ class Plugin1 implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v4-plugin1');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v4-plugin1');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v4-plugin1');
}
}

View File

@ -13,5 +13,16 @@ class Plugin2 implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v4-plugin2');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v4-plugin2');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v4-plugin2');
}
}

View File

@ -10,5 +10,16 @@ class Plugin5 implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v5');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v5');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v5');
}
}

View File

@ -10,5 +10,16 @@ class Plugin6 implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v6');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v6');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v6');
}
}

View File

@ -10,5 +10,16 @@ class Plugin7 implements PluginInterface
{
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v7');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v7');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v7');
}
}

View File

@ -13,6 +13,17 @@ class Plugin8 implements PluginInterface, Capable
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v8');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v8');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v8');
}
public function getCapabilities()

View File

@ -14,5 +14,16 @@ class Plugin implements PluginInterface
public function activate(Composer $composer, IOInterface $io)
{
$io->write('activate v9');
}
public function deactivate(Composer $composer, IOInterface $io)
{
$io->write('deactivate v9');
}
public function uninstall(Composer $composer, IOInterface $io)
{
$io->write('uninstall v9');
}
}

View File

@ -19,6 +19,9 @@ use Composer\Package\CompletePackage;
use Composer\Package\Loader\JsonLoader;
use Composer\Package\Loader\ArrayLoader;
use Composer\Plugin\PluginManager;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\IO\BufferIO;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Test\TestCase;
use Composer\Util\Filesystem;
@ -96,7 +99,7 @@ class PluginInstallerTest extends TestCase
return __DIR__.'/Fixtures/'.$package->getPrettyName();
}));
$this->io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$this->io = new BufferIO();
$dispatcher = $this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$this->autoloadGenerator = new AutoloadGenerator($dispatcher);
@ -108,6 +111,7 @@ class PluginInstallerTest extends TestCase
$this->composer->setRepositoryManager($rm);
$this->composer->setInstallationManager($im);
$this->composer->setAutoloadGenerator($this->autoloadGenerator);
$this->composer->setEventDispatcher(new EventDispatcher($this->composer, $this->io));
$this->pm = new PluginManager($this->io, $this->composer);
$this->composer->setPluginManager($this->pm);
@ -140,6 +144,7 @@ class PluginInstallerTest extends TestCase
$plugins = $this->pm->getPlugins();
$this->assertEquals('installer-v1', $plugins[0]->version);
$this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
}
public function testInstallMultiplePlugins()
@ -158,6 +163,7 @@ class PluginInstallerTest extends TestCase
$this->assertEquals('installer-v4', $plugins[0]->version);
$this->assertEquals('plugin2', $plugins[1]->name);
$this->assertEquals('installer-v4', $plugins[1]->version);
$this->assertEquals('activate v4-plugin1'.PHP_EOL.'activate v4-plugin2'.PHP_EOL, $this->io->getOutput());
}
public function testUpgradeWithNewClassName()
@ -176,7 +182,29 @@ class PluginInstallerTest extends TestCase
$installer->update($this->repository, $this->packages[0], $this->packages[1]);
$plugins = $this->pm->getPlugins();
$this->assertCount(1, $plugins);
$this->assertEquals('installer-v2', $plugins[1]->version);
$this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'activate v2'.PHP_EOL, $this->io->getOutput());
}
public function testUninstall()
{
$this->repository
->expects($this->once())
->method('getPackages')
->will($this->returnValue(array($this->packages[0])));
$this->repository
->expects($this->exactly(1))
->method('hasPackage')
->will($this->onConsecutiveCalls(true, false));
$installer = new PluginInstaller($this->io, $this->composer);
$this->pm->loadInstalledPlugins();
$installer->uninstall($this->repository, $this->packages[0]);
$plugins = $this->pm->getPlugins();
$this->assertCount(0, $plugins);
$this->assertEquals('activate v1'.PHP_EOL.'deactivate v1'.PHP_EOL.'uninstall v1'.PHP_EOL, $this->io->getOutput());
}
public function testUpgradeWithSameClassName()
@ -196,6 +224,7 @@ class PluginInstallerTest extends TestCase
$plugins = $this->pm->getPlugins();
$this->assertEquals('installer-v3', $plugins[1]->version);
$this->assertEquals('activate v2'.PHP_EOL.'deactivate v2'.PHP_EOL.'activate v3'.PHP_EOL, $this->io->getOutput());
}
public function testRegisterPluginOnlyOneTime()
@ -213,6 +242,7 @@ class PluginInstallerTest extends TestCase
$plugins = $this->pm->getPlugins();
$this->assertCount(1, $plugins);
$this->assertEquals('installer-v1', $plugins[0]->version);
$this->assertEquals('activate v1'.PHP_EOL, $this->io->getOutput());
}
/**

View File

@ -227,7 +227,9 @@ class ComposerRepositoryTest extends TestCase
$repository = new ComposerRepository(
array('url' => $repositoryUrl),
new NullIO(),
FactoryMock::createConfig()
FactoryMock::createConfig(),
$this->getMockBuilder('Composer\Util\HttpDownloader')->disableOriginalConstructor()->getMock(),
$this->getMockBuilder('Composer\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock()
);
$object = new \ReflectionObject($repository);

View File

@ -82,11 +82,21 @@ class FilesystemRepositoryTest extends TestCase
$json = $this->createJsonFileMock();
$repository = new FilesystemRepository($json);
$im = $this->getMockBuilder('Composer\Installer\InstallationManager')
->disableOriginalConstructor()
->getMock();
$im->expects($this->once())
->method('getInstallPath')
->will($this->returnValue('/foo/bar/vendor/woop/woop'));
$json
->expects($this->once())
->method('read')
->will($this->returnValue(array()));
$json
->expects($this->once())
->method('getPath')
->will($this->returnValue('/foo/bar/vendor/composer/installed.json'));
$json
->expects($this->once())
->method('exists')
@ -95,11 +105,12 @@ class FilesystemRepositoryTest extends TestCase
->expects($this->once())
->method('write')
->with(array(
array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0'),
'packages' => array(array('name' => 'mypkg', 'type' => 'library', 'version' => '0.1.10', 'version_normalized' => '0.1.10.0', 'install-path' => '../woop/woop')),
'dev' => true,
));
$repository->addPackage($this->getPackage('mypkg', '0.1.10'));
$repository->write();
$repository->write(true, $im);
}
private function createJsonFileMock()

View File

@ -40,15 +40,6 @@ class FossilDriverTest extends TestCase
$fs->removeDirectory($this->home);
}
private function getCmd($cmd)
{
if (Platform::isWindows()) {
return strtr($cmd, "'", '"');
}
return $cmd;
}
public static function supportProvider()
{
return array(

View File

@ -71,15 +71,6 @@ class SvnDriverTest extends TestCase
$svn->initialize();
}
private function getCmd($cmd)
{
if (Platform::isWindows()) {
return strtr($cmd, "'", '"');
}
return $cmd;
}
public static function supportProvider()
{
return array(

View File

@ -0,0 +1,80 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Script;
use Composer\Composer;
use Composer\Config;
use Composer\Script\Event;
use Composer\Test\TestCase;
class EventTest extends TestCase
{
public function testEventSetsOriginatingEvent()
{
$io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$composer = $this->createComposerInstance();
$originatingEvent = new \Composer\EventDispatcher\Event('originatingEvent');
$scriptEvent = new Event('test', $composer, $io, true);
$this->assertNull(
$scriptEvent->getOriginatingEvent(),
'originatingEvent is initialized as null'
);
$scriptEvent->setOriginatingEvent($originatingEvent);
$this->assertSame(
$originatingEvent,
$scriptEvent->getOriginatingEvent(),
'getOriginatingEvent() SHOULD return test event'
);
}
public function testEventCalculatesNestedOriginatingEvent()
{
$io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock();
$composer = $this->createComposerInstance();
$originatingEvent = new \Composer\EventDispatcher\Event('upperOriginatingEvent');
$intermediateEvent = new Event('intermediate', $composer, $io, true);
$intermediateEvent->setOriginatingEvent($originatingEvent);
$scriptEvent = new Event('test', $composer, $io, true);
$scriptEvent->setOriginatingEvent($intermediateEvent);
$this->assertNotSame(
$intermediateEvent,
$scriptEvent->getOriginatingEvent(),
'getOriginatingEvent() SHOULD NOT return intermediate events'
);
$this->assertSame(
$originatingEvent,
$scriptEvent->getOriginatingEvent(),
'getOriginatingEvent() SHOULD return upper-most event'
);
}
private function createComposerInstance()
{
$composer = new Composer;
$config = new Config;
$composer->setConfig($config);
$package = $this->getMockBuilder('Composer\Package\RootPackageInterface')->getMock();
$composer->setPackage($package);
return $composer;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -24,12 +24,9 @@ use RecursiveIteratorIterator;
*/
class GitHubTest extends TestCase
{
private $username = 'username';
private $password = 'password';
private $authcode = 'authcode';
private $message = 'mymessage';
private $origin = 'github.com';
private $token = 'githubtoken';
public function testUsernamePasswordAuthenticationFlow()
{

View File

@ -24,7 +24,6 @@ class GitLabTest extends TestCase
{
private $username = 'username';
private $password = 'password';
private $authcode = 'authcode';
private $message = 'mymessage';
private $origin = 'gitlab.com';
private $token = 'gitlabtoken';

View File

@ -0,0 +1,117 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Test\Util;
use Composer\Util\Zip;
use PHPUnit\Framework\TestCase;
/**
* @author Andreas Schempp <andreas.schempp@terminal42.ch>
*/
class ZipTest extends TestCase
{
public function testThrowsExceptionIfZipExcentionIsNotLoaded()
{
if (extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is loaded.');
}
$this->setExpectedException('\RuntimeException', 'The Zip Util requires PHP\'s zip extension');
Zip::getComposerJson('');
}
public function testReturnsNullifTheZipIsNotFound()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/invalid.zip');
$this->assertNull($result);
}
public function testReturnsNullIfTheZipIsEmpty()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/empty.zip');
$this->assertNull($result);
}
public function testReturnsNullIfTheZipHasNoComposerJson()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/nojson.zip');
$this->assertNull($result);
}
public function testReturnsNullIfTheComposerJsonIsInASubSubfolder()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/subfolder.zip');
$this->assertNull($result);
}
public function testReturnsComposerJsonInZipRoot()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/root.zip');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
public function testReturnsComposerJsonInFirstFolder()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/folder.zip');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
public function testReturnsRootComposerJsonAndSkipsSubfolders()
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The PHP zip extension is not loaded.');
return;
}
$result = Zip::getComposerJson(__DIR__.'/Fixtures/Zip/multiple.zip');
$this->assertEquals("{\n \"name\": \"foo/bar\"\n}\n", $result);
}
}