Merge pull request #329 from Seldaek/new_dev

[BC Break] New dev handling
main
Nils Adermann 13 years ago
commit 39aa5c0752

@ -30,6 +30,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Solver;
use Composer\IO\IOInterface;
@ -113,6 +114,7 @@ EOT
}
// creating requirements request
$installFromLock = false;
$request = new Request($pool);
if ($update) {
$io->write('<info>Updating dependencies</info>');
@ -125,6 +127,7 @@ EOT
$request->install($link->getTarget(), $link->getConstraint());
}
} elseif ($composer->getLocker()->isLocked()) {
$installFromLock = true;
$io->write('<info>Installing from lock file</info>');
if (!$composer->getLocker()->isFresh()) {
@ -185,13 +188,59 @@ EOT
if (!$operations) {
$io->write('<info>Nothing to install/update</info>');
}
// force dev packages to be updated to latest reference on update
if ($update) {
foreach ($localRepo->getPackages() as $package) {
// skip non-dev packages
if (!$package->isDev()) {
continue;
}
// skip packages that will be updated/uninstalled
foreach ($operations as $operation) {
if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage())
|| ('uninstall' === $operation->getJobType() && $package === $operation->getPackage())
) {
continue 2;
}
}
// force update
$newPackage = $composer->getRepositoryManager()->findPackage($package->getName(), $package->getVersion());
if ($newPackage->getSourceReference() !== $package->getSourceReference()) {
$operations[] = new UpdateOperation($package, $newPackage);
}
}
}
foreach ($operations as $operation) {
if ($verbose) {
$io->write((string) $operation);
}
if (!$dryRun) {
$eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
// if installing from lock, restore dev packages' references to their locked state
if ($installFromLock) {
$package = null;
if ('update' === $operation->getJobType()) {
$package = $operation->getTargetPackage();
} elseif ('install' === $operation->getJobType()) {
$package = $operation->getPackage();
}
if ($package && $package->isDev()) {
$lockData = $composer->getLocker()->getLockData();
foreach ($lockData['packages'] as $lockedPackage) {
if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) {
$package->setSourceReference($lockedPackage['source-reference']);
break;
}
}
}
}
$installationManager->execute($operation);
$eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
}
}

@ -125,14 +125,14 @@ class DownloadManager
$sourceType = $package->getSourceType();
$distType = $package->getDistType();
if (!($preferSource && $sourceType) && $distType) {
if (!$package->isDev() && !($preferSource && $sourceType) && $distType) {
$package->setInstallationSource('dist');
} elseif ($sourceType) {
$package->setInstallationSource('source');
} elseif ($package->isDev()) {
throw new \InvalidArgumentException('Dev package '.$package.' must have a source specified');
} else {
throw new \InvalidArgumentException(
'Package '.$package.' should have source or dist specified'
);
throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
}
$fs = new Filesystem();

@ -109,7 +109,9 @@ class LibraryInstaller implements InstallerInterface
$this->downloadManager->update($initial, $target, $downloadPath);
$this->installBinaries($target);
$this->repository->removePackage($initial);
$this->repository->addPackage(clone $target);
if (!$this->repository->hasPackage($target)) {
$this->repository->addPackage(clone $target);
}
}
/**

@ -122,6 +122,8 @@ class ArrayLoader
$package->setSourceType($config['source']['type']);
$package->setSourceUrl($config['source']['url']);
$package->setSourceReference($config['source']['reference']);
} elseif ($package->isDev()) {
throw new \UnexpectedValueException('Dev package '.$package.' must have a source specified');
}
if (isset($config['dist'])) {

@ -38,7 +38,7 @@ class RootPackageLoader extends ArrayLoader
$config['name'] = '__root__';
}
if (!isset($config['version'])) {
$config['version'] = '1.0.0-dev';
$config['version'] = '1.0.0';
}
$package = parent::load($config);

@ -69,11 +69,7 @@ class Locker
*/
public function getLockedPackages()
{
if (!$this->isLocked()) {
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
$lockList = $this->lockFile->read();
$lockList = $this->getLockData();
$packages = array();
foreach ($lockList['packages'] as $info) {
$package = $this->repositoryManager->getLocalRepository()->findPackage($info['package'], $info['version']);
@ -95,6 +91,15 @@ class Locker
return $packages;
}
public function getLockData()
{
if (!$this->isLocked()) {
throw new \LogicException('No lockfile found. Unable to read locked packages');
}
return $this->lockFile->read();
}
/**
* Locks provided packages into lockfile.
*
@ -116,7 +121,13 @@ class Locker
));
}
$lock['packages'][] = array('package' => $name, 'version' => $version);
$spec = array('package' => $name, 'version' => $version);
if ($package->isDev()) {
$spec['source-reference'] = $package->getSourceReference();
}
$lock['packages'][] = $spec;
}
$this->lockFile->write($lock);

@ -41,6 +41,7 @@ class MemoryPackage extends BasePackage
protected $extra = array();
protected $binaries = array();
protected $scripts = array();
protected $dev;
protected $requires = array();
protected $conflicts = array();
@ -63,6 +64,16 @@ class MemoryPackage extends BasePackage
$this->version = $version;
$this->prettyVersion = $prettyVersion;
$this->dev = 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
}
/**
* {@inheritDoc}
*/
public function isDev()
{
return $this->dev;
}
/**

@ -68,6 +68,13 @@ interface PackageInterface
*/
function matches($name, LinkConstraintInterface $constraint);
/**
* Returns whether the package is a development virtual package or a concrete one
*
* @return Boolean
*/
function isDev();
/**
* Returns the package type, e.g. library
*

@ -34,10 +34,15 @@ class VersionParser
{
$version = trim($version);
if (preg_match('{^(?:master|trunk|default)(?:[.-]?dev)?$}i', $version)) {
// match master-like branches
if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
return '9999999-dev';
}
if ('dev-' === strtolower(substr($version, 0, 4))) {
return strtolower($version);
}
// match classical versioning
if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.$this->modifierRegex.'$}i', $version, $matches)) {
$version = $matches[1]
@ -53,7 +58,7 @@ class VersionParser
// add version modifiers if a version was matched
if (isset($index)) {
if (!empty($matches[$index])) {
$mod = array('{^pl?$}', '{^rc$}');
$mod = array('{^pl?$}i', '{^rc$}i');
$modNormalized = array('patch', 'RC');
$version .= '-'.preg_replace($mod, $modNormalized, strtolower($matches[$index]))
. (!empty($matches[$index+1]) ? $matches[$index+1] : '');
@ -97,7 +102,7 @@ class VersionParser
return str_replace('x', '9999999', $version).'-dev';
}
throw new \UnexpectedValueException('Invalid branch name '.$name);
return 'dev-'.$name;
}
/**

@ -76,20 +76,22 @@ class VcsRepository extends ArrayRepository
}
foreach ($driver->getTags() as $tag => $identifier) {
$this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)', false);
$msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)';
if ($debug) {
$this->io->write($msg);
} else {
$this->io->overwrite($msg, false);
}
$parsedTag = $this->validateTag($versionParser, $tag);
if ($parsedTag && $driver->hasComposerFile($identifier)) {
try {
$data = $driver->getComposerInformation($identifier);
} catch (\Exception $e) {
if (strpos($e->getMessage(), 'JSON Parse Error') !== false) {
if ($debug) {
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
}
continue;
} else {
throw $e;
if ($debug) {
$this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
}
continue;
}
// manually versioned package
@ -103,7 +105,7 @@ class VcsRepository extends ArrayRepository
// make sure tag packages have no -dev flag
$data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
$data['version_normalized'] = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']);
$data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
// broken package, version doesn't match tag
if ($data['version_normalized'] !== $parsedTag) {
@ -126,39 +128,33 @@ class VcsRepository extends ArrayRepository
$this->io->overwrite('', false);
foreach ($driver->getBranches() as $branch => $identifier) {
$this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)', false);
$msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)';
if ($debug) {
$this->io->write($msg);
} else {
$this->io->overwrite($msg, false);
}
$parsedBranch = $this->validateBranch($versionParser, $branch);
if ($driver->hasComposerFile($identifier)) {
$data = $driver->getComposerInformation($identifier);
// manually versioned package
if (isset($data['version'])) {
$data['version_normalized'] = $versionParser->normalize($data['version']);
} elseif ($parsedBranch) {
// auto-versionned package, read value from branch name
$data['version'] = $branch;
$data['version_normalized'] = $parsedBranch;
} else {
if (!$parsedBranch) {
if ($debug) {
$this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found');
}
continue;
}
// make sure branch packages have a -dev flag
$normalizedStableVersion = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']);
$data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']) . '-dev';
$data['version_normalized'] = $normalizedStableVersion . '-dev';
// branches are always auto-versionned, read value from branch name
$data['version'] = $branch;
$data['version_normalized'] = $parsedBranch;
// Skip branches that contain a version that has been tagged already
foreach ($this->getPackages() as $package) {
if ($normalizedStableVersion === $package->getVersion()) {
if ($debug) {
$this->io->write('Skipped branch '.$branch.', already tagged');
}
continue 2;
}
// make sure branch packages have a dev flag
if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
$data['version'] = 'dev-' . $data['version'];
} else {
$data['version'] = $data['version'] . '-dev';
}
if ($debug) {

@ -67,9 +67,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
->method('getPackages')
->will($this->returnValue(array($this->packages[0])));
$this->repository
->expects($this->once())
->expects($this->exactly(2))
->method('hasPackage')
->will($this->returnValue(true));
->will($this->onConsecutiveCalls(true, false));
$installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
$test = $this;
@ -90,9 +90,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
->method('getPackages')
->will($this->returnValue(array($this->packages[1])));
$this->repository
->expects($this->once())
->expects($this->exactly(2))
->method('hasPackage')
->will($this->returnValue(true));
->will($this->onConsecutiveCalls(true, false));
$installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
$test = $this;

@ -128,10 +128,9 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue('package1'));
$this->repository
->expects($this->exactly(2))
->expects($this->exactly(3))
->method('hasPackage')
->with($initial)
->will($this->onConsecutiveCalls(true, false));
->will($this->onConsecutiveCalls(true, false, false));
$this->dm
->expects($this->once())

@ -49,9 +49,10 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'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('master', '9999999-dev'),
'parses trunk' => array('trunk', '9999999-dev'),
'parses trunk/2' => array('trunk-dev', '9999999-dev'),
'parses master' => array('dev-master', '9999999-dev'),
'parses trunk' => array('dev-trunk', '9999999-dev'),
'parses arbitrary' => array('dev-feature-foo', 'dev-feature-foo'),
'parses arbitrary2' => array('DEV-FOOBAR', 'dev-foobar'),
);
}
@ -72,6 +73,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'invalid chars' => array('a'),
'invalid type' => array('1.0.0-meh'),
'too many bits' => array('1.0.0.0.0'),
'non-dev arbitrary' => array('feature-foo'),
);
}
@ -97,6 +99,8 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'parses long digits/2' => array('2.4.4', '2.4.4.9999999-dev'),
'parses master' => array('master', '9999999-dev'),
'parses trunk' => array('trunk', '9999999-dev'),
'parses arbitrary' => array('feature-a', 'dev-feature-a'),
'parses arbitrary/2' => array('foobar', 'dev-foobar'),
);
}
@ -121,8 +125,9 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'no op means eq' => array('1.2.3', new VersionConstraint('=', '1.2.3.0')),
'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')),
'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')),
'accepts master' => array('>=master-dev', new VersionConstraint('>=', '9999999-dev')),
'accepts master/2' => array('master-dev', new VersionConstraint('=', '9999999-dev')),
'accepts master' => array('>=dev-master', new VersionConstraint('>=', '9999999-dev')),
'accepts master/2' => array('dev-master', new VersionConstraint('=', '9999999-dev')),
'accepts arbitrary' => array('dev-feature-a', new VersionConstraint('=', 'dev-feature-a')),
);
}

@ -0,0 +1,140 @@
<?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\Json;
use Symfony\Component\Process\ExecutableFinder;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Repository\VcsRepository;
use Composer\Repository\Vcs\GitDriver;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;
use Composer\IO\NullIO;
class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
{
private static $gitRepo;
private static $skipped;
public static function setUpBeforeClass()
{
$oldCwd = getcwd();
self::$gitRepo = sys_get_temp_dir() . '/composer-git-'.rand().'/';
$locator = new ExecutableFinder();
if (!$locator->find('git')) {
self::$skipped = 'This test needs a git binary in the PATH to be able to run';
return;
}
if (!mkdir(self::$gitRepo) || !chdir(self::$gitRepo)) {
self::$skipped = 'Could not create and move into the temp git repo '.self::$gitRepo;
return;
}
// init
$process = new ProcessExecutor;
$process->execute('git init', $null);
touch('foo');
$process->execute('git add foo', $null);
$process->execute('git commit -m init', $null);
// non-composed tag & branch
$process->execute('git tag 0.5.0', $null);
$process->execute('git branch oldbranch', $null);
// add composed tag & master branch
$composer = array('name' => 'a/b');
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m addcomposer', $null);
$process->execute('git tag 0.6.0', $null);
// add feature-a branch
$process->execute('git checkout -b feature-a', $null);
file_put_contents('foo', 'bar feature');
$process->execute('git add foo', $null);
$process->execute('git commit -m change-a', $null);
// add version to composer.json
$process->execute('git checkout master', $null);
$composer['version'] = '1.0.0';
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m addversion', $null);
// create tag with wrong version in it
$process->execute('git tag 0.9.0', $null);
// create tag with correct version in it
$process->execute('git tag 1.0.0', $null);
// add feature-b branch
$process->execute('git checkout -b feature-b', $null);
file_put_contents('foo', 'baz feature');
$process->execute('git add foo', $null);
$process->execute('git commit -m change-b', $null);
// add 1.0 branch
$process->execute('git checkout master', $null);
$process->execute('git branch 1.0', $null);
// add 1.0.x branch
$process->execute('git branch 1.0.x', $null);
// update master to 2.0
$composer['version'] = '2.0.0';
file_put_contents('composer.json', json_encode($composer));
$process->execute('git add composer.json', $null);
$process->execute('git commit -m bump-version', $null);
chdir($oldCwd);
}
public function setUp()
{
if (self::$skipped) {
$this->markTestSkipped(self::$skipped);
}
}
public static function tearDownAfterClass()
{
$fs = new Filesystem;
$fs->removeDirectory(self::$gitRepo);
}
public function testLoadVersions()
{
$expected = array(
'0.6.0' => true,
'1.0.0' => true,
'1.0-dev' => true,
'1.0.x-dev' => true,
'dev-feature-b' => true,
'dev-feature-a' => true,
'dev-master' => true,
);
$repo = new VcsRepository(array('url' => self::$gitRepo), new NullIO);
$packages = $repo->getPackages();
$dumper = new ArrayDumper();
foreach ($packages as $package) {
if (isset($expected[$package->getPrettyVersion()])) {
unset($expected[$package->getPrettyVersion()]);
} else {
$this->fail('Unexpected version '.$package->getPrettyVersion().' in '.json_encode($dumper->dump($package)));
}
}
$this->assertEmpty($expected, 'Missing versions: '.implode(', ', array_keys($expected)));
}
}
Loading…
Cancel
Save