diff --git a/doc/03-cli.md b/doc/03-cli.md index 1d0a3cea2..8c1dfee70 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -22,6 +22,8 @@ The following options are available with every command: * **--quiet (-q):** Do not output any message. * **--no-interaction (-n):** Do not ask any interactive question. * **--no-plugins:** Disables plugins. +* **--no-cache:** Disables the use of the cache directory. Same as setting the COMPOSER_CACHE_DIR + env var to /dev/null (or NUL on Windows). * **--working-dir (-d):** If specified, use the given directory as working directory. * **--profile:** Display timing and memory usage information * **--ansi:** Force ANSI output. @@ -491,7 +493,7 @@ php composer.phar validate ### Options -* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound version constraints. +* **--no-check-all:** Do not emit a warning if requirements in `composer.json` use unbound or overly strict version constraints. * **--no-check-lock:** Do not emit an error if `composer.lock` exists and is not up to date. * **--no-check-publish:** Do not emit an error if `composer.json` is unsuitable for publishing as a package on Packagist but is otherwise valid. * **--with-dependencies:** Also validate the composer.json of all installed dependencies. diff --git a/src/Composer/Autoload/AutoloadGenerator.php b/src/Composer/Autoload/AutoloadGenerator.php index 0db4015c3..7ea1a3444 100644 --- a/src/Composer/Autoload/AutoloadGenerator.php +++ b/src/Composer/Autoload/AutoloadGenerator.php @@ -940,9 +940,13 @@ INITIALIZER; $packageMap, function ($item) use ($include) { $package = $item[0]; - $name = $package->getName(); + foreach ($package->getNames() as $name) { + if (isset($include[$name])) { + return true; + } + } - return isset($include[$name]); + return false; } ); } diff --git a/src/Composer/Cache.php b/src/Composer/Cache.php index 44395c3a2..3f2861797 100644 --- a/src/Composer/Cache.php +++ b/src/Composer/Cache.php @@ -44,7 +44,7 @@ class Cache $this->whitelist = $whitelist; $this->filesystem = $filesystem ?: new Filesystem(); - if (preg_match('{(^|[\\\\/])(\$null|NUL|/dev/null)([\\\\/]|$)}', $cacheDir)) { + if (!self::isUsable($cacheDir)) { $this->enabled = false; return; @@ -59,6 +59,11 @@ class Cache } } + public static function isUsable($path) + { + return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path); + } + public function isEnabled() { return $this->enabled; diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index c8278c03c..c4942c03c 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -557,7 +557,12 @@ EOT $finder = new ExecutableFinder(); $gitBin = $finder->find('git'); - $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); + // TODO in v3 always call with an array + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $cmd = new Process(array($gitBin, 'config', '-l')); + } else { + $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin))); + } $cmd->run(); if ($cmd->isSuccessful()) { diff --git a/src/Composer/Command/ValidateCommand.php b/src/Composer/Command/ValidateCommand.php index b86fd92ea..52023e528 100644 --- a/src/Composer/Command/ValidateCommand.php +++ b/src/Composer/Command/ValidateCommand.php @@ -39,7 +39,7 @@ class ValidateCommand extends BaseCommand ->setName('validate') ->setDescription('Validates a composer.json and composer.lock.') ->setDefinition(array( - new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not make a complete validation'), + new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'), new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'), new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'), new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'), diff --git a/src/Composer/Config/JsonConfigSource.php b/src/Composer/Config/JsonConfigSource.php index 128ebf8ec..15d40d200 100644 --- a/src/Composer/Config/JsonConfigSource.php +++ b/src/Composer/Config/JsonConfigSource.php @@ -193,6 +193,10 @@ class JsonConfigSource implements ConfigSourceInterface { $this->manipulateJson('removeSubNode', $type, $name, function (&$config, $type, $name) { unset($config[$type][$name]); + + if (0 === count($config[$type])) { + unset($config[$type]); + } }); } diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index c89375ce4..0bfee80f3 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -118,6 +118,11 @@ class Application extends BaseApplication ))); ErrorHandler::register($io); + if ($input->hasParameterOption('--no-cache')) { + $io->writeError('Disabling cache usage', true, IOInterface::DEBUG); + putenv('COMPOSER_CACHE_DIR='.(Platform::isWindows() ? 'nul' : '/dev/null')); + } + // switch working dir if ($newWorkDir = $this->getNewWorkingDir($input)) { $oldWorkingDir = getcwd(); @@ -272,7 +277,7 @@ class Application extends BaseApplication } if (isset($startTime)) { - $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MB), time: '.round(microtime(true) - $startTime, 2).'s'); + $io->writeError('Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s'); } restore_error_handler(); @@ -457,6 +462,7 @@ class Application extends BaseApplication $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information')); $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.')); $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.')); + $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache')); return $definition; } diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index ff398f300..96e47cb22 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -19,6 +19,7 @@ use Composer\Util\Filesystem; use Composer\Util\Git as GitUtil; use Composer\Util\Platform; use Composer\Util\ProcessExecutor; +use Composer\Cache; /** * @author Jordi Boggiano @@ -51,7 +52,7 @@ class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface $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', '>=')) { + 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 { diff --git a/src/Composer/IO/ConsoleIO.php b/src/Composer/IO/ConsoleIO.php index c0f235659..8b29177d5 100644 --- a/src/Composer/IO/ConsoleIO.php +++ b/src/Composer/IO/ConsoleIO.php @@ -153,7 +153,7 @@ class ConsoleIO extends BaseIO $memoryUsage = memory_get_usage() / 1024 / 1024; $timeSpent = microtime(true) - $this->startTime; $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) { - return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message); + return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message); }, (array) $messages); } diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 259e20057..cd7facdd2 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -1294,11 +1294,6 @@ class Installer $rootRequires = array_merge($rootRequires, $rootDevRequires); - $requiredPackageNames = array(); - foreach ($rootRequires as $require) { - $requiredPackageNames[] = $require->getTarget(); - } - $skipPackages = array(); if (!$this->whitelistAllDependencies) { foreach ($rootRequires as $require) { @@ -1317,11 +1312,17 @@ class Installer $packageQueue = new \SplQueue; $depPackages = $repositorySet->findPackages($packageName, null, false); - - $nameMatchesRequiredPackage = in_array($packageName, $requiredPackageNames, true); + $matchesByPattern = array(); // check if the name is a glob pattern that did not match directly - if (!$nameMatchesRequiredPackage) { + if (empty($depPackages)) { + // add any installed package matching the whitelisted name/pattern + $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$'); + foreach ($localOrLockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) { + $matchesByPattern[] = $repositorySet->findPackages($installedPackage['name'], null, false); + } + + // add root requirements which match the whitelisted name/pattern $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName); foreach ($rootRequiredPackageNames as $rootRequiredPackageName) { if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) { @@ -1331,6 +1332,10 @@ class Installer } } + if (!empty($matchesByPattern)) { + $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern)); + } + if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) { $this->io->writeError('Package "' . $packageName . '" listed for update is not installed. Ignoring.'); } @@ -1362,7 +1367,7 @@ class Installer continue; } - if (isset($skipPackages[$requirePackage->getName()])) { + if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) { $this->io->writeError('Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.'); continue; } diff --git a/src/Composer/Package/BasePackage.php b/src/Composer/Package/BasePackage.php index 65ea6860f..f2f5be707 100644 --- a/src/Composer/Package/BasePackage.php +++ b/src/Composer/Package/BasePackage.php @@ -239,12 +239,13 @@ abstract class BasePackage implements PackageInterface * Build a regexp from a package name, expanding * globs as required * * @param string $whiteListedPattern + * @param bool $wrap Wrap the cleaned string by the given string * @return string */ - public static function packageNameToRegexp($whiteListedPattern) + public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i') { $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern)); - return "{^" . $cleanedWhiteListedPattern . "$}i"; + return sprintf($wrap, $cleanedWhiteListedPattern); } } diff --git a/src/Composer/Repository/Vcs/FossilDriver.php b/src/Composer/Repository/Vcs/FossilDriver.php index cc872474e..491fafa86 100644 --- a/src/Composer/Repository/Vcs/FossilDriver.php +++ b/src/Composer/Repository/Vcs/FossilDriver.php @@ -12,6 +12,7 @@ namespace Composer\Repository\Vcs; +use Composer\Cache; use Composer\Config; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; @@ -45,6 +46,10 @@ class FossilDriver extends VcsDriver if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) { $this->checkoutDir = $this->url; } else { + if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $localName = preg_replace('{[^a-z0-9]}i', '-', $this->url); $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil'; $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/'; diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index 0269f4721..4a14974fb 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -41,6 +41,10 @@ class GitDriver extends VcsDriver $this->repoDir = $this->url; $cacheUrl = realpath($this->url); } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/'; GitUtil::cleanEnv(); diff --git a/src/Composer/Repository/Vcs/HgDriver.php b/src/Composer/Repository/Vcs/HgDriver.php index 45f13d5fe..373b24af1 100644 --- a/src/Composer/Repository/Vcs/HgDriver.php +++ b/src/Composer/Repository/Vcs/HgDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Config; +use Composer\Cache; use Composer\Util\Hg as HgUtils; use Composer\Util\ProcessExecutor; use Composer\Util\Filesystem; @@ -37,6 +38,10 @@ class HgDriver extends VcsDriver if (Filesystem::isLocalPath($this->url)) { $this->repoDir = $this->url; } else { + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $cacheDir = $this->config->get('cache-vcs-dir'); $this->repoDir = $cacheDir . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/'; diff --git a/src/Composer/Repository/Vcs/PerforceDriver.php b/src/Composer/Repository/Vcs/PerforceDriver.php index 667f914df..09b5d4b16 100644 --- a/src/Composer/Repository/Vcs/PerforceDriver.php +++ b/src/Composer/Repository/Vcs/PerforceDriver.php @@ -13,6 +13,7 @@ namespace Composer\Repository\Vcs; use Composer\Config; +use Composer\Cache; use Composer\IO\IOInterface; use Composer\Util\ProcessExecutor; use Composer\Util\Perforce; @@ -54,6 +55,10 @@ class PerforceDriver extends VcsDriver return; } + if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) { + throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled'); + } + $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot; $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io); } diff --git a/src/Composer/Util/Perforce.php b/src/Composer/Util/Perforce.php index b064feec4..31ddeffec 100644 --- a/src/Composer/Util/Perforce.php +++ b/src/Composer/Util/Perforce.php @@ -370,7 +370,13 @@ class Perforce public function windowsLogin($password) { $command = $this->generateP4Command(' login -a'); - $process = new Process($command, null, null, $password); + + // TODO in v3 generate command as an array + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $process = Process::fromShellCommandline($command, null, null, $password); + } else { + $process = new Process($command, null, null, $password); + } return $process->run(); } diff --git a/src/Composer/Util/ProcessExecutor.php b/src/Composer/Util/ProcessExecutor.php index 2a0742778..f5e1ef610 100644 --- a/src/Composer/Util/ProcessExecutor.php +++ b/src/Composer/Util/ProcessExecutor.php @@ -62,7 +62,13 @@ class ProcessExecutor $this->captureOutput = func_num_args() > 1; $this->errorOutput = null; - $process = new Process($command, $cwd, null, null, static::getTimeout()); + + // TODO in v3, commands should be passed in as arrays of cmd + args + if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) { + $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout()); + } else { + $process = new Process($command, $cwd, null, null, static::getTimeout()); + } $callback = is_callable($output) ? $output : array($this, 'outputHandler'); $process->run($callback); diff --git a/tests/Composer/Test/ApplicationTest.php b/tests/Composer/Test/ApplicationTest.php index 5f491440b..8739a5004 100644 --- a/tests/Composer/Test/ApplicationTest.php +++ b/tests/Composer/Test/ApplicationTest.php @@ -31,6 +31,11 @@ class ApplicationTest extends TestCase ->with($this->equalTo('--no-plugins')) ->will($this->returnValue(true)); + $inputMock->expects($this->at($index++)) + ->method('hasParameterOption') + ->with($this->equalTo('--no-cache')) + ->will($this->returnValue(false)); + $inputMock->expects($this->at($index++)) ->method('getParameterOption') ->with($this->equalTo(array('--working-dir', '-d'))) @@ -84,6 +89,11 @@ class ApplicationTest extends TestCase ->with($this->equalTo('--no-plugins')) ->will($this->returnValue(true)); + $inputMock->expects($this->at($index++)) + ->method('hasParameterOption') + ->with($this->equalTo('--no-cache')) + ->will($this->returnValue(false)); + $inputMock->expects($this->at($index++)) ->method('getParameterOption') ->with($this->equalTo(array('--working-dir', '-d'))) diff --git a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php index 4d672084e..c1605bf97 100644 --- a/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php +++ b/tests/Composer/Test/Autoload/AutoloadGeneratorTest.php @@ -14,6 +14,7 @@ namespace Composer\Test\Autoload; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Link; +use Composer\Semver\Constraint\Constraint; use Composer\Util\Filesystem; use Composer\Package\AliasPackage; use Composer\Package\Package; @@ -419,6 +420,72 @@ class AutoloadGeneratorTest extends TestCase $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); } + public function testNonDevAutoloadShouldIncludeReplacedPackages() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array(new Link('a', 'a/a'))); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + + $a->setRequires(array(new Link('a/a', 'b/c'))); + + $b->setAutoload(array('psr-4' => array('B\\' => 'src/'))); + $b->setReplaces( + array(new Link('b/b', 'b/c', new Constraint('==', '1.0'), 'replaces')) + ); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src/C'); + file_put_contents($this->vendorDir.'/b/b/src/C/C.php', 'generator->dump($this->config, $this->repository, $package, $this->im, 'composer', true, '_5'); + + $this->assertEquals( + array( + 'B\\C\\C' => $this->vendorDir.'/b/b/src/C/C.php', + ), + include $this->vendorDir.'/composer/autoload_classmap.php' + ); + } + + public function testNonDevAutoloadExclusionWithRecursionReplace() + { + $package = new Package('a', '1.0', '1.0'); + $package->setRequires(array( + new Link('a', 'a/a'), + )); + + $packages = array(); + $packages[] = $a = new Package('a/a', '1.0', '1.0'); + $packages[] = $b = new Package('b/b', '1.0', '1.0'); + $a->setAutoload(array('psr-0' => array('A' => 'src/', 'A\\B' => 'lib/'))); + $a->setRequires(array( + new Link('a/a', 'c/c'), + )); + $b->setAutoload(array('psr-0' => array('B\\Sub\\Name' => 'src/'))); + $b->setReplaces(array( + new Link('b/b', 'c/c'), + )); + + $this->repository->expects($this->once()) + ->method('getCanonicalPackages') + ->will($this->returnValue($packages)); + + $this->fs->ensureDirectoryExists($this->vendorDir.'/composer'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/lib'); + $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b/src'); + + $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_5'); + $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer'); + $this->assertFileExists($this->vendorDir.'/composer/autoload_classmap.php', "ClassMap file needs to be generated, even if empty."); + } + public function testPSRToClassMapIgnoresNonExistingDir() { $package = new Package('a', '1.0', '1.0'); diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test new file mode 100644 index 000000000..8ea177cad --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-all-dependencies.test @@ -0,0 +1,46 @@ +--TEST-- +Update with a package whitelist pattern and all-dependencies flag updates packages and their dependencies, even if defined as root dependency, matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-all-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test new file mode 100644 index 000000000..c685f14ce --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-dependencies.test @@ -0,0 +1,49 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern but no dependencies defined as roo package +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*", "root-dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "root-dependency", "version": "1.1.0" }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "root-dependency": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "root-dependency", "version": "1.0.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test new file mode 100644 index 000000000..a24bafb91 --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-with-root-dependencies.test @@ -0,0 +1,55 @@ +--TEST-- +Update with a package whitelist only updates those packages and their dependencies matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0", "require": { "whitelisted-component2": "1.1.0" } }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.1.0", "whitelisted-component5": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.1.0", "require": { "whitelisted-component4": "1.1.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.1.0" }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.1.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "whitelisted-component3": "1.0.0", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0", "require": { "whitelisted-component2": "1.0.0" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "whitelisted-component3", "version": "1.0.0", "require": { "whitelisted-component4": "1.0.0" } }, + { "name": "whitelisted-component4", "version": "1.0.0" }, + { "name": "whitelisted-component5", "version": "1.0.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* --with-dependencies +--EXPECT-- +Updating dependency (1.0.0) to dependency (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0) +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) diff --git a/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test new file mode 100644 index 000000000..e5551b43f --- /dev/null +++ b/tests/Composer/Test/Fixtures/installer/update-whitelist-patterns-without-dependencies.test @@ -0,0 +1,44 @@ +--TEST-- +Update with a package whitelist only updates those packages matching the pattern +--COMPOSER-- +{ + "repositories": [ + { + "type": "package", + "package": [ + { "name": "fixed", "version": "1.1.0" }, + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.1.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.1.0", "require": { "dependency": "1.*" } }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.*" } }, + { "name": "dependency", "version": "1.1.0" }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.1.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.1.0" }, + { "name": "unrelated-dependency", "version": "1.0.0" } + ] + } + ], + "require": { + "fixed": "1.*", + "whitelisted-component1": "1.*", + "whitelisted-component2": "1.*", + "unrelated": "1.*" + } +} +--INSTALLED-- +[ + { "name": "fixed", "version": "1.0.0" }, + { "name": "whitelisted-component1", "version": "1.0.0" }, + { "name": "whitelisted-component2", "version": "1.0.0", "require": { "dependency": "1.0.0" } }, + { "name": "dependency", "version": "1.0.0" }, + { "name": "unrelated", "version": "1.0.0", "require": { "unrelated-dependency": "1.*" } }, + { "name": "unrelated-dependency", "version": "1.0.0" } +] +--RUN-- +update whitelisted-* +--EXPECT-- +Updating whitelisted-component1 (1.0.0) to whitelisted-component1 (1.1.0) +Updating whitelisted-component2 (1.0.0) to whitelisted-component2 (1.1.0)