From be5eae58529c32405ea9d7cbe44d65d84bad0bd7 Mon Sep 17 00:00:00 2001 From: David Neilsen Date: Thu, 31 Jul 2014 10:55:59 +1200 Subject: [PATCH 01/13] Clear all the caches --- src/Composer/Command/ClearCacheCommand.php | 35 ++++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index f49b00bc3..cf25491d3 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -40,21 +40,30 @@ EOT { $config = Factory::createConfig(); $io = $this->getIO(); - - $cachePath = realpath($config->get('cache-repo-dir')); - if (!$cachePath) { - $io->write('Cache directory does not exist.'); - return; + + $cachePaths = array( + $config->get('cache-dir'), + $config->get('cache-files-dir'), + $config->get('cache-repo-dir'), + $config->get('cache-vcs-dir'), + ); + + foreach ($cachePaths as $cachePath) { + $cachePath = realpath($cachePath); + if (!$cachePath) { + $io->write('Cache directory does not exist.'); + return; + } + $cache = new Cache($io, $cachePath); + if (!$cache->isEnabled()) { + $io->write('Cache is not enabled.'); + return; + } + + $io->write('Clearing cache in: '.$cachePath.''); + $cache->gc(0, 0); } - $cache = new Cache($io, $cachePath); - if (!$cache->isEnabled()) { - $io->write('Cache is not enabled.'); - return; - } - - $io->write('Clearing cache in: '.$cachePath.''); - $cache->gc(0, 0); $io->write('Cache cleared.'); } } From 957f498419dec86ab08b80e5e45c855a653f9a5f Mon Sep 17 00:00:00 2001 From: David Neilsen Date: Wed, 13 Aug 2014 09:18:03 +1200 Subject: [PATCH 02/13] Update clear cache command to be more verbose --- src/Composer/Command/ClearCacheCommand.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Composer/Command/ClearCacheCommand.php b/src/Composer/Command/ClearCacheCommand.php index cf25491d3..47d139144 100644 --- a/src/Composer/Command/ClearCacheCommand.php +++ b/src/Composer/Command/ClearCacheCommand.php @@ -42,28 +42,28 @@ EOT $io = $this->getIO(); $cachePaths = array( - $config->get('cache-dir'), - $config->get('cache-files-dir'), - $config->get('cache-repo-dir'), - $config->get('cache-vcs-dir'), + 'cache-dir' => $config->get('cache-dir'), + 'cache-files-dir' => $config->get('cache-files-dir'), + 'cache-repo-dir' => $config->get('cache-repo-dir'), + 'cache-vcs-dir' => $config->get('cache-vcs-dir'), ); - foreach ($cachePaths as $cachePath) { + foreach ($cachePaths as $key => $cachePath) { $cachePath = realpath($cachePath); if (!$cachePath) { - $io->write('Cache directory does not exist.'); + $io->write("Cache directory does not exist ($key): $cachePath"); return; } $cache = new Cache($io, $cachePath); if (!$cache->isEnabled()) { - $io->write('Cache is not enabled.'); + $io->write("Cache is not enabled ($key): $cachePath"); return; } - $io->write('Clearing cache in: '.$cachePath.''); + $io->write("Clearing cache ($key): $cachePath"); $cache->gc(0, 0); } - $io->write('Cache cleared.'); + $io->write('All caches cleared.'); } } From 947db97e337c48f34c6699c11ffc77ccc388e54b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 5 Jul 2014 13:50:47 -0500 Subject: [PATCH 03/13] [#2492] Removing an unused variable and use statement, fixing phpdoc --- src/Composer/Command/CreateProjectCommand.php | 1 - src/Composer/DependencyResolver/Pool.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index 632e869df..b37814413 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -242,7 +242,6 @@ EOT } $parser = new VersionParser(); - $candidates = array(); $requirements = $parser->parseNameVersionPairs(array($packageName)); $name = strtolower($requirements[0]['name']); if (!$packageVersion && isset($requirements[0]['version'])) { diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index 636a78c0a..d0c660f82 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -15,7 +15,6 @@ namespace Composer\DependencyResolver; use Composer\Package\BasePackage; use Composer\Package\AliasPackage; use Composer\Package\Version\VersionParser; -use Composer\Package\Link; use Composer\Package\LinkConstraint\LinkConstraintInterface; use Composer\Package\LinkConstraint\VersionConstraint; use Composer\Package\LinkConstraint\EmptyConstraint; @@ -25,6 +24,7 @@ use Composer\Repository\ComposerRepository; use Composer\Repository\InstalledRepositoryInterface; use Composer\Repository\StreamableRepositoryInterface; use Composer\Repository\PlatformRepository; +use Composer\Package\PackageInterface; /** * A package pool contains repositories that provide packages. @@ -232,7 +232,7 @@ class Pool * packages must match or null to return all * @param bool $mustMatchName Whether the name of returned packages * must match the given name - * @return array A set of packages + * @return PackageInterface[] A set of packages */ public function whatProvides($name, LinkConstraintInterface $constraint = null, $mustMatchName = false) { From 58535a62fa906c73668d27de61494c23238ba84e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 5 Jul 2014 13:51:15 -0500 Subject: [PATCH 04/13] [#2492] Automatically using the latest version when requiring a package This applies to the init and require commands. Previously: If you ommitted the version of a library, it prompted you to enter a version. New Behavior: If you omit the version, it automatically selects the latest version that is consistent with your minimum-stability flag. Is Jordi mentions, this is consistent with how npm works. --- src/Composer/Command/CreateProjectCommand.php | 18 +++--- src/Composer/Command/InitCommand.php | 63 +++++++++++++++---- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index b37814413..e8201cb2b 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -266,23 +266,14 @@ EOT $pool->addRepository($sourceRepo); $constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null; - $candidates = $pool->whatProvides($name, $constraint); - foreach ($candidates as $key => $candidate) { - if ($candidate->getName() !== $name) { - unset($candidates[$key]); - } - } + $candidates = $pool->whatProvides($name, $constraint, true); if (!$candidates) { throw new \InvalidArgumentException("Could not find package $name" . ($packageVersion ? " with version $packageVersion." : " with stability $stability.")); } - if (null === $directory) { - $parts = explode("/", $name, 2); - $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); - } - // select highest version if we have many + // logic is repeated in InitCommand $package = reset($candidates); foreach ($candidates as $candidate) { if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { @@ -291,6 +282,11 @@ EOT } unset($candidates); + if (null === $directory) { + $parts = explode("/", $name, 2); + $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); + } + $io->write('Installing ' . $package->getName() . ' (' . VersionParser::formatVersion($package, false) . ')'); if ($disablePlugins) { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index a44546b73..0fa0d7564 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -12,6 +12,7 @@ namespace Composer\Command; +use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Factory; use Composer\Package\BasePackage; @@ -32,6 +33,7 @@ class InitCommand extends Command { private $gitConfig; private $repos; + private $pool; public function parseAuthorString($author) { @@ -284,9 +286,11 @@ EOT protected function findPackages($name) { - $packages = array(); + return $this->getRepos()->search($name); + } - // init repos + protected function getRepos() + { if (!$this->repos) { $this->repos = new CompositeRepository(array_merge( array(new PlatformRepository), @@ -294,7 +298,17 @@ EOT )); } - return $this->repos->search($name); + return $this->repos; + } + + protected function getPool() + { + if (!$this->pool) { + $this->pool = new Pool($this->getComposer()->getPackage()->getMinimumStability()); + $this->pool->addRepository($this->getRepos()); + } + + return $this->pool; } protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array()) @@ -306,15 +320,42 @@ EOT $requires = $this->normalizeRequirements($requires); $result = array(); - foreach ($requires as $key => $requirement) { - if (!isset($requirement['version']) && $input->isInteractive()) { - $question = $dialog->getQuestion('Please provide a version constraint for the '.$requirement['name'].' requirement'); - if ($constraint = $dialog->ask($output, $question)) { - $requirement['version'] = $constraint; - } - } + foreach ($requires as $requirement) { if (!isset($requirement['version'])) { - throw new \InvalidArgumentException('The requirement '.$requirement['name'].' must contain a version constraint'); + + $candidates = $this->getPool()->whatProvides($requirement['name'], null, true); + + if (!$candidates) { + throw new \InvalidArgumentException(sprintf( + 'Could not find any versions for package "%s". Perhaps the name is wrong?', + $requirement['name'] + )); + } + + // select highest version if we have many + // logic is repeated in CreateProjectCommand + $package = reset($candidates); + foreach ($candidates as $candidate) { + if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { + $package = $candidate; + } + } + + if (!$package) { + throw new \Exception(sprintf( + 'No version of the package "%s" could be found that meets your minimum stability requirements of "%s"', + $requirement['name'], + $this->getComposer()->getPackage()->getMinimumStability() + )); + } + + $requirement['version'] = $package->getPrettyVersion(); + + $output->writeln(sprintf( + 'Using version %s for %s', + $requirement['version'], + $requirement['name'] + )); } $result[] = $requirement['name'] . ' ' . $requirement['version']; From 26179cc4b49fc18ac9276b90f6b6a47103e8a36d Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sat, 5 Jul 2014 14:09:03 -0500 Subject: [PATCH 05/13] [#2492] Prefixed real versions with ~ when guessing the latest version 2.1.0 > ~2.1.0 v2.1.0 -> ~2.1.0 dev-master -> dev-master --- src/Composer/Command/InitCommand.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 0fa0d7564..f0f41a006 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -349,7 +349,17 @@ EOT )); } - $requirement['version'] = $package->getPrettyVersion(); + $version = $package->getPrettyVersion(); + if (!$package->isDev()) { + // remove the v prefix if there is one + if (substr($version, 0, 1) == 'v') { + $version = substr($version, 1); + } + + // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1 + $version = '~'.$version; + } + $requirement['version'] = $version; $output->writeln(sprintf( 'Using version %s for %s', From aea2e901a93f6923417d01fe03f6910137ae557c Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 14 Jul 2014 21:25:44 -0500 Subject: [PATCH 06/13] Extracting logic into a new class related to selecting the latest version Also refactored InitCommand slightly so that you can use this "latest version" functionality when searching for a package as well. --- src/Composer/Command/CreateProjectCommand.php | 18 +--- src/Composer/Command/InitCommand.php | 93 +++++++++++-------- .../Package/Version/VersionSelector.php | 71 ++++++++++++++ .../Package/Version/VersionSelectorTest.php | 71 ++++++++++++++ 4 files changed, 201 insertions(+), 52 deletions(-) create mode 100644 src/Composer/Package/Version/VersionSelector.php create mode 100644 tests/Composer/Test/Package/Version/VersionSelectorTest.php diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index e8201cb2b..9928692eb 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -21,6 +21,7 @@ use Composer\IO\IOInterface; use Composer\Package\BasePackage; use Composer\DependencyResolver\Pool; use Composer\DependencyResolver\Operation\InstallOperation; +use Composer\Package\Version\VersionSelector; use Composer\Repository\ComposerRepository; use Composer\Repository\CompositeRepository; use Composer\Repository\FilesystemRepository; @@ -265,23 +266,14 @@ EOT $pool = new Pool($stability); $pool->addRepository($sourceRepo); - $constraint = $packageVersion ? $parser->parseConstraints($packageVersion) : null; - $candidates = $pool->whatProvides($name, $constraint, true); + // find the latest version if there are multiple + $versionSelector = new VersionSelector($pool); + $package = $versionSelector->findBestCandidate($name, $packageVersion); - if (!$candidates) { + if (!$package) { throw new \InvalidArgumentException("Could not find package $name" . ($packageVersion ? " with version $packageVersion." : " with stability $stability.")); } - // select highest version if we have many - // logic is repeated in InitCommand - $package = reset($candidates); - foreach ($candidates as $candidate) { - if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { - $package = $candidate; - } - } - unset($candidates); - if (null === $directory) { $parts = explode("/", $name, 2); $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts); diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index f0f41a006..b0ce567a8 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -16,6 +16,7 @@ use Composer\DependencyResolver\Pool; use Composer\Json\JsonFile; use Composer\Factory; use Composer\Package\BasePackage; +use Composer\Package\Version\VersionSelector; use Composer\Repository\CompositeRepository; use Composer\Repository\PlatformRepository; use Composer\Package\Version\VersionParser; @@ -323,42 +324,8 @@ EOT foreach ($requires as $requirement) { if (!isset($requirement['version'])) { - $candidates = $this->getPool()->whatProvides($requirement['name'], null, true); - - if (!$candidates) { - throw new \InvalidArgumentException(sprintf( - 'Could not find any versions for package "%s". Perhaps the name is wrong?', - $requirement['name'] - )); - } - - // select highest version if we have many - // logic is repeated in CreateProjectCommand - $package = reset($candidates); - foreach ($candidates as $candidate) { - if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { - $package = $candidate; - } - } - - if (!$package) { - throw new \Exception(sprintf( - 'No version of the package "%s" could be found that meets your minimum stability requirements of "%s"', - $requirement['name'], - $this->getComposer()->getPackage()->getMinimumStability() - )); - } - - $version = $package->getPrettyVersion(); - if (!$package->isDev()) { - // remove the v prefix if there is one - if (substr($version, 0, 1) == 'v') { - $version = substr($version, 1); - } - - // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1 - $version = '~'.$version; - } + // determine the best version automatically + $version = $this->findBestVersionForPackage($requirement['name']); $requirement['version'] = $version; $output->writeln(sprintf( @@ -420,7 +387,7 @@ EOT $package = $dialog->askAndValidate($output, $dialog->getQuestion('Enter package # to add, or the complete package name if it is not listed', false, ':'), $validator, 3); } - // no constraint yet, prompt user + // no constraint yet, determine the best version automatically if (false !== $package && false === strpos($package, ' ')) { $validator = function ($input) { $input = trim($input); @@ -428,9 +395,20 @@ EOT return $input ?: false; }; - $constraint = $dialog->askAndValidate($output, $dialog->getQuestion('Enter the version constraint to require', false, ':'), $validator, 3); + $constraint = $dialog->askAndValidate( + $output, + $dialog->getQuestion('Enter the version constraint to require (or leave blank to use the latest version)', false, ':'), + $validator, + 3) + ; if (false === $constraint) { - continue; + $constraint = $this->findBestVersionForPackage($package); + + $output->writeln(sprintf( + 'Using version %s for %s', + $constraint, + $package + )); } $package .= ' '.$constraint; @@ -555,4 +533,41 @@ EOT return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } + + /** + * Given a package name, this determines the best version to use in the require key. + * + * This returns a version with the ~ operator prefixed when possible. + * + * @param string $name + * @return string + * @throws \InvalidArgumentException + */ + protected function findBestVersionForPackage($name) + { + // find the latest version allowed in this pool + $versionSelector = new VersionSelector($this->getPool()); + $package = $versionSelector->findBestCandidate($name); + + if (!$package) { + throw new \InvalidArgumentException(sprintf( + 'Could not find package %s at any version for your minimum-stability (%s). Check the package spelling or your minimum-stability', + $name, + $this->getComposer()->getPackage()->getMinimumStability() + )); + } + + $version = $package->getPrettyVersion(); + if (!$package->isDev()) { + // remove the v prefix if there is one + if (substr($version, 0, 1) == 'v') { + $version = substr($version, 1); + } + + // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1 + $version = '~'.$version; + } + + return $version; + } } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php new file mode 100644 index 000000000..ad9c2ab71 --- /dev/null +++ b/src/Composer/Package/Version/VersionSelector.php @@ -0,0 +1,71 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Package\Version; + +use Composer\DependencyResolver\Pool; +use Composer\Package\PackageInterface; + +/** + * Selects the best possible version for a package + * + * @author Ryan Weaver + */ +class VersionSelector +{ + private $pool; + + private $parser; + + public function __construct(Pool $pool) + { + $this->pool = $pool; + } + + /** + * Given a package name and optional version, returns the latest PackageInterface + * that matches. + * + * @param string $packageName + * @param string $targetPackageVersion + * @return PackageInterface|bool + */ + public function findBestCandidate($packageName, $targetPackageVersion = null) + { + $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null; + $candidates = $this->pool->whatProvides($packageName, $constraint, true); + + if (!$candidates) { + return false; + } + + // select highest version if we have many + // logic is repeated in InitCommand + $package = reset($candidates); + foreach ($candidates as $candidate) { + if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { + $package = $candidate; + } + } + + return $package; + } + + private function getParser() + { + if ($this->parser === null) { + $this->parser = new VersionParser(); + } + + return $this->parser; + } +} diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php new file mode 100644 index 000000000..636645807 --- /dev/null +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -0,0 +1,71 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Package\Version; + +use Composer\Package\Version\VersionSelector; + +class VersionSelectorTest extends \PHPUnit_Framework_TestCase +{ + // A) multiple versions, get the latest one + // B) targetPackageVersion will pass to pool + // C) No results, throw exception + + public function testLatestVersionIsReturned() + { + $packageName = 'foobar'; + + $package1 = $this->createMockPackage('1.2.1'); + $package2 = $this->createMockPackage('1.2.2'); + $package3 = $this->createMockPackage('1.2.0'); + $packages = array($package1, $package2, $package3); + + $pool = $this->createMockPool(); + $pool->expects($this->once()) + ->method('whatProvides') + ->with($packageName, null, true) + ->will($this->returnValue($packages)); + + $versionSelector = new VersionSelector($pool); + $best = $versionSelector->findBestCandidate($packageName); + + // 1.2.2 should be returned because it's the latest of the returned versions + $this->assertEquals($package2, $best, 'Latest version should be 1.2.2'); + } + + public function testFalseReturnedOnNoPackages() + { + $pool = $this->createMockPool(); + $pool->expects($this->once()) + ->method('whatProvides') + ->will($this->returnValue(array())); + + $versionSelector = new VersionSelector($pool); + $best = $versionSelector->findBestCandidate('foobaz'); + $this->assertFalse($best, 'No versions are available returns false'); + } + + private function createMockPackage($version) + { + $package = $this->getMock('\Composer\Package\PackageInterface'); + $package->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue($version)); + + return $package; + } + + private function createMockPool() + { + return $this->getMock('Composer\DependencyResolver\Pool', array(), array(), '', true); + } +} From 895e62e85991dfb6f20d7cdc9aed2430c44fd742 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 12 Sep 2014 11:23:20 -0400 Subject: [PATCH 07/13] Refactoring selection of the "recommended" version (e.g ~1.2) and adding some tests This also modifies the behavior slightly (from a recommendation by seldaek) to always propose the minor version of the recommendation (e.g. ~1.2 instead of ~1.2.1). --- src/Composer/Command/InitCommand.php | 13 +----- .../Package/Version/VersionSelector.php | 42 ++++++++++++++++++ .../Package/Version/VersionSelectorTest.php | 44 +++++++++++++++++++ 3 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index b0ce567a8..37c4d3029 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -557,17 +557,6 @@ EOT )); } - $version = $package->getPrettyVersion(); - if (!$package->isDev()) { - // remove the v prefix if there is one - if (substr($version, 0, 1) == 'v') { - $version = substr($version, 1); - } - - // 2.1.0 -> ~2.1.0, 2.0-beta.1 -> ~2.0-beta.1 - $version = '~'.$version; - } - - return $version; + return $versionSelector->findRecommendedRequireVersion($package); } } diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index ad9c2ab71..c00c4254d 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -60,6 +60,48 @@ class VersionSelector return $package; } + /** + * Given a concrete version, this returns a ~ constraint (when possible) + * that should be used, for example, in composer.json. + * + * For example: + * * 1.2.1 -> ~1.2 + * * 1.2 -> ~1.2 + * * v3.2.1 -> ~3.2 + * * 2.0-beta.1 -> ~2.0-beta.1 + * * dev-master -> dev-master (dev versions are untouched) + * + * @param PackageInterface $package + * @return string + */ + public function findRecommendedRequireVersion(PackageInterface $package) + { + $version = $package->getPrettyVersion(); + if (!$package->isDev()) { + // remove the v prefix if there is one + if (substr($version, 0, 1) == 'v') { + $version = substr($version, 1); + } + + // for stable packages only, we try to transform 2.1.1 to 2.1 + // this allows you to upgrade through minor versions + if ($package->getStability() == 'stable') { + $semanticVersionParts = explode('.', $version); + // check to see if we have a normal 1.2.6 semantic version + if (count($semanticVersionParts) == 3) { + // remove the last part (i.e. the patch version number) + unset($semanticVersionParts[2]); + $version = implode('.', $semanticVersionParts); + } + } + + // 2.1 -> ~2.1 + $version = '~'.$version; + } + + return $version; + } + private function getParser() { if ($this->parser === null) { diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index 636645807..fc4d6d61b 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -54,6 +54,50 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase $this->assertFalse($best, 'No versions are available returns false'); } + /** + * @dataProvider getRecommendedRequireVersionPackages + */ + public function testFindRecommendedRequireVersion($prettyVersion, $isDev, $stability, $expectedVersion) + { + $pool = $this->createMockPool(); + $versionSelector = new VersionSelector($pool); + + $package = $this->getMock('\Composer\Package\PackageInterface'); + $package->expects($this->any()) + ->method('getPrettyVersion') + ->will($this->returnValue($prettyVersion)); + $package->expects($this->any()) + ->method('isDev') + ->will($this->returnValue($isDev)); + $package->expects($this->any()) + ->method('getStability') + ->will($this->returnValue($stability)); + + $recommended = $versionSelector->findRecommendedRequireVersion($package); + + // assert that the recommended version is what we expect + $this->assertEquals($expectedVersion, $recommended); + } + + public function getRecommendedRequireVersionPackages() + { + return array( + // real version, is dev package, stability, expected recommendation + array('1.2.1', false, 'stable', '~1.2'), + array('1.2', false, 'stable', '~1.2'), + array('v1.2.1', false, 'stable', '~1.2'), + array('3.1.2-pl2', false, 'stable', '~3.1'), + array('3.1.2-patch', false, 'stable', '~3.1'), + // for non-stable versions, we add ~, but don't try the (1.2.1 -> 1.2) transformation + array('2.0-beta.1', false, 'beta', '~2.0-beta.1'), + array('3.1.2-alpha5', false, 'alpha', '~3.1.2-alpha5'), + array('3.0-RC2', false, 'RC', '~3.0-RC2'), + // dev packages are not touched at all + array('dev-master', true, 'dev', 'dev-master'), + array('3.1.2-dev', true, 'dev', '3.1.2-dev'), + ); + } + private function createMockPackage($version) { $package = $this->getMock('\Composer\Package\PackageInterface'); From 6c6a623229431e90c12b5fb0b6991e0a0b58ef76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pluchino?= Date: Mon, 22 Sep 2014 11:58:26 +0200 Subject: [PATCH 08/13] Fix invalid version format --- .../Package/Version/VersionParser.php | 6 ++ .../Package/Version/VersionParserTest.php | 57 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 812ccf5ad..3825426a8 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -122,6 +122,12 @@ class VersionParser } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)'.self::$modifierRegex.'$}i', $version, $matches)) { // match date-based versioning $version = preg_replace('{\D}', '-', $matches[1]); $index = 2; + } elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?'.self::$modifierRegex.'$}i', $version, $matches)) { + $version = $matches[1] + .(!empty($matches[2]) ? $matches[2] : '.0') + .(!empty($matches[3]) ? $matches[3] : '.0') + .(!empty($matches[4]) ? $matches[4] : '.0'); + $index = 5; } // add version modifiers if a version was matched diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 464779d83..f54371d65 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -79,34 +79,35 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase public function successfulNormalizedVersions() { return array( - 'none' => array('1.0.0', '1.0.0.0'), - 'none/2' => array('1.2.3.4', '1.2.3.4'), - 'parses state' => array('1.0.0RC1dev', '1.0.0.0-RC1-dev'), - 'CI parsing' => array('1.0.0-rC15-dev', '1.0.0.0-RC15-dev'), - 'delimiters' => array('1.0.0.RC.15-dev', '1.0.0.0-RC15-dev'), - 'RC uppercase' => array('1.0.0-rc1', '1.0.0.0-RC1'), - 'patch replace' => array('1.0.0.pl3-dev', '1.0.0.0-patch3-dev'), - 'forces w.x.y.z' => array('1.0-dev', '1.0.0.0-dev'), - 'forces w.x.y.z/2' => array('0', '0.0.0.0'), - 'parses long' => array('10.4.13-beta', '10.4.13.0-beta'), - 'expand shorthand' => array('10.4.13-b', '10.4.13.0-beta'), - 'expand shorthand2' => array('10.4.13-b5', '10.4.13.0-beta5'), - 'strips leading v' => array('v1.0.0', '1.0.0.0'), - 'strips v/datetime' => array('v20100102', '20100102'), - 'parses dates y-m' => array('2010.01', '2010-01'), - 'parses dates w/ .' => array('2010.01.02', '2010-01-02'), - 'parses dates w/ -' => array('2010-01-02', '2010-01-02'), - 'parses numbers' => array('2010-01-02.5', '2010-01-02-5'), - 'parses datetime' => array('20100102-203040', '20100102-203040'), - 'parses dt+number' => array('20100102203040-10', '20100102203040-10'), - 'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'), - 'parses master' => array('dev-master', '9999999-dev'), - 'parses trunk' => array('dev-trunk', '9999999-dev'), - 'parses branches' => array('1.x-dev', '1.9999999.9999999.9999999-dev'), - 'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'), - 'parses arbitrary2' => array('DEV-FOOBAR', 'dev-FOOBAR'), - 'parses arbitrary3' => array('dev-feature/foo', 'dev-feature/foo'), - 'ignores aliases' => array('dev-master as 1.0.0', '9999999-dev'), + 'none' => array('1.0.0', '1.0.0.0'), + 'none/2' => array('1.2.3.4', '1.2.3.4'), + 'parses state' => array('1.0.0RC1dev', '1.0.0.0-RC1-dev'), + 'CI parsing' => array('1.0.0-rC15-dev', '1.0.0.0-RC15-dev'), + 'delimiters' => array('1.0.0.RC.15-dev', '1.0.0.0-RC15-dev'), + 'RC uppercase' => array('1.0.0-rc1', '1.0.0.0-RC1'), + 'patch replace' => array('1.0.0.pl3-dev', '1.0.0.0-patch3-dev'), + 'forces w.x.y.z' => array('1.0-dev', '1.0.0.0-dev'), + 'forces w.x.y.z/2' => array('0', '0.0.0.0'), + 'parses long' => array('10.4.13-beta', '10.4.13.0-beta'), + 'expand shorthand' => array('10.4.13-b', '10.4.13.0-beta'), + 'expand shorthand2' => array('10.4.13-b5', '10.4.13.0-beta5'), + 'strips leading v' => array('v1.0.0', '1.0.0.0'), + 'strips v/datetime' => array('v20100102', '20100102'), + 'parses dates y-m' => array('2010.01', '2010-01'), + 'parses dates w/ .' => array('2010.01.02', '2010-01-02'), + 'parses dates w/ -' => array('2010-01-02', '2010-01-02'), + 'parses numbers' => array('2010-01-02.5', '2010-01-02-5'), + 'parses dates y.m.Y' => array('2010.1.555', '2010.1.555.0'), + 'parses datetime' => array('20100102-203040', '20100102-203040'), + 'parses dt+number' => array('20100102203040-10', '20100102203040-10'), + 'parses dt+patch' => array('20100102-203040-p1', '20100102-203040-patch1'), + 'parses master' => array('dev-master', '9999999-dev'), + 'parses trunk' => array('dev-trunk', '9999999-dev'), + 'parses branches' => array('1.x-dev', '1.9999999.9999999.9999999-dev'), + 'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'), + 'parses arbitrary2' => array('DEV-FOOBAR', 'dev-FOOBAR'), + 'parses arbitrary3' => array('dev-feature/foo', 'dev-feature/foo'), + 'ignores aliases' => array('dev-master as 1.0.0', '9999999-dev'), ); } From 2c237fdfdfa18cdc69813783a1cc9c219efd1d78 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Sep 2014 15:09:55 +0100 Subject: [PATCH 09/13] Handle files in Filesystem::copyThenRemove, fixes #3287 --- src/Composer/Util/Filesystem.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Composer/Util/Filesystem.php b/src/Composer/Util/Filesystem.php index 182004205..6044c25d6 100644 --- a/src/Composer/Util/Filesystem.php +++ b/src/Composer/Util/Filesystem.php @@ -234,6 +234,13 @@ class Filesystem */ public function copyThenRemove($source, $target) { + if (!is_dir($source)) { + copy($source, $target); + $this->unlink($source); + + return; + } + $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS); $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST); $this->ensureDirectoryExists($target); From 4d522e40fbd770aff93aec75145cf77a5ef27293 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Sep 2014 16:05:43 +0100 Subject: [PATCH 10/13] Load auth when updating a git repo mirror, fixes #3243 --- src/Composer/Repository/Vcs/GitDriver.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Composer/Repository/Vcs/GitDriver.php b/src/Composer/Repository/Vcs/GitDriver.php index f37be1e9a..20cfa93fc 100644 --- a/src/Composer/Repository/Vcs/GitDriver.php +++ b/src/Composer/Repository/Vcs/GitDriver.php @@ -56,16 +56,22 @@ class GitDriver extends VcsDriver throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.'); } + $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); + // update the repo if it is a valid git repository if (is_dir($this->repoDir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $this->repoDir) && trim($output) === '.') { - if (0 !== $this->process->execute('git remote update --prune origin', $output, $this->repoDir)) { - $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')'); + try { + $commandCallable = function ($url) { + return sprintf('git remote set-url origin %s && git remote update --prune origin', escapeshellarg($url)); + }; + $gitUtil->runCommand($commandCallable, $this->url, $this->repoDir); + } catch (\Exception $e) { + $this->io->write('Failed to update '.$this->url.', package information from this repository may be outdated ('.$e->getMessage().')'); } } else { // clean up directory and do a fresh clone into it $fs->removeDirectory($this->repoDir); - $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs); $repoDir = $this->repoDir; $commandCallable = function ($url) use ($repoDir) { return sprintf('git clone --mirror %s %s', escapeshellarg($url), escapeshellarg($repoDir)); From 62b506214640ae66ad88a07240a0bbb0201bd1e3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 22 Sep 2014 17:04:58 +0100 Subject: [PATCH 11/13] Improve sorting of plugin packages, fixes #3109, refs #2972 --- src/Composer/Installer.php | 16 +++++++++++++--- .../installer/plugins-are-installed-first.test | 11 +++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Composer/Installer.php b/src/Composer/Installer.php index 3176ece17..5191d8558 100644 --- a/src/Composer/Installer.php +++ b/src/Composer/Installer.php @@ -597,9 +597,19 @@ class Installer continue; } - if ($package->getRequires() === array() && ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer')) { - $installerOps[] = $op; - unset($operations[$idx]); + if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') { + // ignore requirements to platform or composer-plugin-api + $requires = array_keys($package->getRequires()); + foreach ($requires as $index => $req) { + if ($req === 'composer-plugin-api' || preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) { + unset($requires[$index]); + } + } + // if there are no other requirements, move the plugin to the top of the op list + if (!count($requires)) { + $installerOps[] = $op; + unset($operations[$idx]); + } } } diff --git a/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test index c57a36d35..ad34e9c02 100644 --- a/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test +++ b/tests/Composer/Test/Fixtures/installer/plugins-are-installed-first.test @@ -1,5 +1,5 @@ --TEST-- -Composer installers are installed first if they have no requirements +Composer installers are installed first if they have no meaningful requirements --COMPOSER-- { "repositories": [ @@ -9,20 +9,23 @@ Composer installers are installed first if they have no requirements { "name": "pkg", "version": "1.0.0" }, { "name": "pkg2", "version": "1.0.0" }, { "name": "inst", "version": "1.0.0", "type": "composer-plugin" }, - { "name": "inst2", "version": "1.0.0", "type": "composer-plugin", "require": { "pkg2": "*" } } + { "name": "inst-with-req", "version": "1.0.0", "type": "composer-plugin", "require": { "php": ">=5", "ext-json": "*", "composer-plugin-api": "*" } }, + { "name": "inst-with-req2", "version": "1.0.0", "type": "composer-plugin", "require": { "pkg2": "*" } } ] } ], "require": { "pkg": "1.0.0", "inst": "1.0.0", - "inst2": "1.0.0" + "inst-with-req2": "1.0.0", + "inst-with-req": "1.0.0" } } --RUN-- install --EXPECT-- Installing inst (1.0.0) +Installing inst-with-req (1.0.0) Installing pkg (1.0.0) Installing pkg2 (1.0.0) -Installing inst2 (1.0.0) +Installing inst-with-req2 (1.0.0) From b7b0901f87a3dc293027a343ae868dc3c24458fa Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 23 Sep 2014 15:17:53 +0100 Subject: [PATCH 12/13] Allow using new code in the init command and avoid initializing the composer instance too early, refs #3096 --- src/Composer/Command/InitCommand.php | 49 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 37c4d3029..d7ee60677 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -302,16 +302,6 @@ EOT return $this->repos; } - protected function getPool() - { - if (!$this->pool) { - $this->pool = new Pool($this->getComposer()->getPackage()->getMinimumStability()); - $this->pool->addRepository($this->getRepos()); - } - - return $this->pool; - } - protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array()) { $dialog = $this->getHelperSet()->get('dialog'); @@ -325,7 +315,7 @@ EOT if (!isset($requirement['version'])) { // determine the best version automatically - $version = $this->findBestVersionForPackage($requirement['name']); + $version = $this->findBestVersionForPackage($input, $requirement['name']); $requirement['version'] = $version; $output->writeln(sprintf( @@ -402,7 +392,7 @@ EOT 3) ; if (false === $constraint) { - $constraint = $this->findBestVersionForPackage($package); + $constraint = $this->findBestVersionForPackage($input, $package); $output->writeln(sprintf( 'Using version %s for %s', @@ -534,26 +524,53 @@ EOT return false !== filter_var($email, FILTER_VALIDATE_EMAIL); } + private function getPool(InputInterface $input) + { + if (!$this->pool) { + $this->pool = new Pool($this->getMinimumStability($input)); + $this->pool->addRepository($this->getRepos()); + } + + return $this->pool; + } + + private function getMinimumStability(InputInterface $input) + { + if ($input->hasOption('stability')) { + return $input->getOption('stability') ?: 'stable'; + } + + $file = Factory::getComposerFile(); + if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) { + if (!empty($composer['minimum-stability'])) { + return $composer['minimum-stability']; + } + } + + return 'stable'; + } + /** * Given a package name, this determines the best version to use in the require key. * * This returns a version with the ~ operator prefixed when possible. * - * @param string $name + * @param InputInterface $input + * @param string $name * @return string * @throws \InvalidArgumentException */ - protected function findBestVersionForPackage($name) + private function findBestVersionForPackage(InputInterface $input, $name) { // find the latest version allowed in this pool - $versionSelector = new VersionSelector($this->getPool()); + $versionSelector = new VersionSelector($this->getPool($input)); $package = $versionSelector->findBestCandidate($name); if (!$package) { throw new \InvalidArgumentException(sprintf( 'Could not find package %s at any version for your minimum-stability (%s). Check the package spelling or your minimum-stability', $name, - $this->getComposer()->getPackage()->getMinimumStability() + $this->getMinimumStability($input) )); } From e6165e649524f2d8edec6101fd740dd4dfeef7cc Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Tue, 23 Sep 2014 15:19:23 +0100 Subject: [PATCH 13/13] Tweak rules to allow guessing based on branch aliases and avoid greedy constraints on alpha/beta/RC packages, refs #3096 --- .../Package/Version/VersionSelector.php | 64 ++++++++++++------- .../Package/Version/VersionSelectorTest.php | 29 +++++++-- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/Composer/Package/Version/VersionSelector.php b/src/Composer/Package/Version/VersionSelector.php index c00c4254d..29358027f 100644 --- a/src/Composer/Package/Version/VersionSelector.php +++ b/src/Composer/Package/Version/VersionSelector.php @@ -14,6 +14,8 @@ namespace Composer\Package\Version; use Composer\DependencyResolver\Pool; use Composer\Package\PackageInterface; +use Composer\Package\Loader\ArrayLoader; +use Composer\Package\Dumper\ArrayDumper; /** * Selects the best possible version for a package @@ -49,7 +51,6 @@ class VersionSelector } // select highest version if we have many - // logic is repeated in InitCommand $package = reset($candidates); foreach ($candidates as $candidate) { if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) { @@ -68,7 +69,8 @@ class VersionSelector * * 1.2.1 -> ~1.2 * * 1.2 -> ~1.2 * * v3.2.1 -> ~3.2 - * * 2.0-beta.1 -> ~2.0-beta.1 + * * 2.0-beta.1 -> ~2.0@beta + * * dev-master -> ~2.1@dev (dev version with alias) * * dev-master -> dev-master (dev versions are untouched) * * @param PackageInterface $package @@ -76,30 +78,46 @@ class VersionSelector */ public function findRecommendedRequireVersion(PackageInterface $package) { - $version = $package->getPrettyVersion(); + $version = $package->getVersion(); if (!$package->isDev()) { - // remove the v prefix if there is one - if (substr($version, 0, 1) == 'v') { - $version = substr($version, 1); - } - - // for stable packages only, we try to transform 2.1.1 to 2.1 - // this allows you to upgrade through minor versions - if ($package->getStability() == 'stable') { - $semanticVersionParts = explode('.', $version); - // check to see if we have a normal 1.2.6 semantic version - if (count($semanticVersionParts) == 3) { - // remove the last part (i.e. the patch version number) - unset($semanticVersionParts[2]); - $version = implode('.', $semanticVersionParts); - } - } - - // 2.1 -> ~2.1 - $version = '~'.$version; + return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability()); } - return $version; + $loader = new ArrayLoader($this->getParser()); + $dumper = new ArrayDumper(); + $extra = $loader->getBranchAlias($dumper->dump($package)); + if ($extra) { + $extra = preg_replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count); + if ($count) { + $extra = str_replace('.9999999', '.0', $extra); + return $this->transformVersion($extra, $extra, 'dev'); + } + } + + return $package->getPrettyVersion(); + } + + private function transformVersion($version, $prettyVersion, $stability) + { + // attempt to transform 2.1.1 to 2.1 + // this allows you to upgrade through minor versions + $semanticVersionParts = explode('.', $version); + // check to see if we have a semver-looking version + if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) { + // remove the last parts (i.e. the patch version number and any extra) + unset($semanticVersionParts[2], $semanticVersionParts[3]); + $version = implode('.', $semanticVersionParts); + } else { + return $prettyVersion; + } + + // append stability flag if not default + if ($stability != 'stable') { + $version .= '@'.$stability; + } + + // 2.1 -> ~2.1 + return '~'.$version; } private function getParser() diff --git a/tests/Composer/Test/Package/Version/VersionSelectorTest.php b/tests/Composer/Test/Package/Version/VersionSelectorTest.php index fc4d6d61b..2a9cb45a0 100644 --- a/tests/Composer/Test/Package/Version/VersionSelectorTest.php +++ b/tests/Composer/Test/Package/Version/VersionSelectorTest.php @@ -13,6 +13,7 @@ namespace Composer\Test\Package\Version; use Composer\Package\Version\VersionSelector; +use Composer\Package\Version\VersionParser; class VersionSelectorTest extends \PHPUnit_Framework_TestCase { @@ -57,15 +58,19 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase /** * @dataProvider getRecommendedRequireVersionPackages */ - public function testFindRecommendedRequireVersion($prettyVersion, $isDev, $stability, $expectedVersion) + public function testFindRecommendedRequireVersion($prettyVersion, $isDev, $stability, $expectedVersion, $branchAlias = null) { $pool = $this->createMockPool(); $versionSelector = new VersionSelector($pool); + $versionParser = new VersionParser(); $package = $this->getMock('\Composer\Package\PackageInterface'); $package->expects($this->any()) ->method('getPrettyVersion') ->will($this->returnValue($prettyVersion)); + $package->expects($this->any()) + ->method('getVersion') + ->will($this->returnValue($versionParser->normalize($prettyVersion))); $package->expects($this->any()) ->method('isDev') ->will($this->returnValue($isDev)); @@ -73,6 +78,11 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase ->method('getStability') ->will($this->returnValue($stability)); + $branchAlias = $branchAlias === null ? array() : array('branch-alias' => array($prettyVersion => $branchAlias)); + $package->expects($this->any()) + ->method('getExtra') + ->will($this->returnValue($branchAlias)); + $recommended = $versionSelector->findRecommendedRequireVersion($package); // assert that the recommended version is what we expect @@ -82,19 +92,26 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase public function getRecommendedRequireVersionPackages() { return array( - // real version, is dev package, stability, expected recommendation + // real version, is dev package, stability, expected recommendation, [branch-alias] array('1.2.1', false, 'stable', '~1.2'), array('1.2', false, 'stable', '~1.2'), array('v1.2.1', false, 'stable', '~1.2'), array('3.1.2-pl2', false, 'stable', '~3.1'), array('3.1.2-patch', false, 'stable', '~3.1'), // for non-stable versions, we add ~, but don't try the (1.2.1 -> 1.2) transformation - array('2.0-beta.1', false, 'beta', '~2.0-beta.1'), - array('3.1.2-alpha5', false, 'alpha', '~3.1.2-alpha5'), - array('3.0-RC2', false, 'RC', '~3.0-RC2'), - // dev packages are not touched at all + array('2.0-beta.1', false, 'beta', '~2.0@beta'), + array('3.1.2-alpha5', false, 'alpha', '~3.1@alpha'), + array('3.0-RC2', false, 'RC', '~3.0@RC'), + // date-based versions are not touched at all + array('v20121020', false, 'stable', 'v20121020'), + array('v20121020.2', false, 'stable', 'v20121020.2'), + // dev packages without alias are not touched at all array('dev-master', true, 'dev', 'dev-master'), array('3.1.2-dev', true, 'dev', '3.1.2-dev'), + // dev packages with alias inherit the alias + array('dev-master', true, 'dev', '~2.1@dev', '2.1.x-dev'), + array('dev-master', true, 'dev', '~2.1@dev', '2.1.3.x-dev'), + array('dev-master', true, 'dev', '~2.0@dev', '2.x-dev'), ); }