Преглед изворни кода

Merge branch 'master' of https://github.com/composer/composer

digitalkaoz пре 13 година
родитељ
комит
a1619788ba
30 измењених фајлова са 570 додато и 90 уклоњено
  1. 2 2
      doc/01-basic-usage.md
  2. 4 5
      doc/04-schema.md
  3. 2 2
      doc/06-community.md
  4. 52 12
      src/Composer/Command/InstallCommand.php
  5. 2 2
      src/Composer/Command/ShowCommand.php
  6. 11 0
      src/Composer/DependencyResolver/DefaultPolicy.php
  7. 2 2
      src/Composer/DependencyResolver/Solver.php
  8. 13 3
      src/Composer/Downloader/GitDownloader.php
  9. 6 0
      src/Composer/Factory.php
  10. 19 4
      src/Composer/Installer/InstallationManager.php
  11. 236 0
      src/Composer/Package/AliasPackage.php
  12. 10 0
      src/Composer/Package/BasePackage.php
  13. 7 13
      src/Composer/Package/Loader/ArrayLoader.php
  14. 15 0
      src/Composer/Package/Loader/RootPackageLoader.php
  15. 10 2
      src/Composer/Package/Locker.php
  16. 17 0
      src/Composer/Package/MemoryPackage.php
  17. 7 0
      src/Composer/Package/PackageInterface.php
  18. 5 0
      src/Composer/Package/Version/VersionParser.php
  19. 9 2
      src/Composer/Repository/ArrayRepository.php
  20. 2 2
      src/Composer/Repository/CompositeRepository.php
  21. 4 3
      src/Composer/Repository/RepositoryInterface.php
  22. 19 0
      src/Composer/Repository/RepositoryManager.php
  23. 1 0
      src/Composer/Repository/Vcs/HgDriver.php
  24. 14 2
      src/Composer/Util/ProcessExecutor.php
  25. 60 4
      tests/Composer/Test/Downloader/GitDownloaderTest.php
  26. 4 3
      tests/Composer/Test/Package/LockerTest.php
  27. 2 0
      tests/Composer/Test/Package/Version/VersionParserTest.php
  28. 3 3
      tests/Composer/Test/Repository/ArrayRepositoryTest.php
  29. 24 24
      tests/Composer/Test/Repository/CompositeRepositoryTest.php
  30. 8 0
      tests/Composer/Test/Util/ProcessExecutorTest.php

+ 2 - 2
doc/01-basic-usage.md

@@ -136,8 +136,8 @@ packagist.
 
 ## Autoloading
 
-For libraries that follow the [PSR-0](https://github.com/php-fig/fig-
-standards/blob/master/accepted/PSR-0.md) naming standard, composer generates a
+For libraries that follow the [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
+naming standard, composer generates a
 `vendor/.composer/autoload.php` file for autoloading. You can simply include
 this file and you will get autoloading for free.
 

+ 4 - 5
doc/04-schema.md

@@ -6,9 +6,8 @@ This chapter will explain all of the options available in `composer.json`.
 
 We have a [JSON schema](http://json-schema.org) that documents the format and
 can also be used to validate your `composer.json`. In fact, it is used by the
-`validate` command. You can find it at: [`Resources/composer-
-schema.json`](https://github.com/composer/composer/blob/master/res
-/composer-schema.json).
+`validate` command. You can find it at: 
+[`Resources/composer-schema.json`](https://github.com/composer/composer/blob/master/res/composer-schema.json).
 
 ## Package root
 
@@ -188,8 +187,8 @@ Optional.
 
 Autoload mapping for a PHP autoloader.
 
-Currently only [PSR-0](https://github.com/php-fig/fig-
-standards/blob/master/accepted/PSR-0.md) autoloading is supported. Under the
+Currently only [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
+autoloading is supported. Under the
 `psr-0` key you define a mapping from namespaces to paths, relative to the
 package root.
 

+ 2 - 2
doc/06-community.md

@@ -22,7 +22,7 @@ The most important guidelines are described as follows:
 
 ## IRC / mailing list
 
-The developer mailing list is on [google groups](http://groups.google.com/group
-/composer-dev) IRC channels are available for discussion as well, on
+The developer mailing list is on [google groups](http://groups.google.com/group/composer-dev/) 
+IRC channels are available for discussion as well, on
 irc.freenode.org [#composer](irc://irc.freenode.org/composer) for users and
 [#composer-dev](irc://irc.freenode.org/composer-dev) for development.

+ 52 - 12
src/Composer/Command/InstallCommand.php

@@ -20,9 +20,12 @@ use Composer\DependencyResolver;
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\Request;
 use Composer\DependencyResolver\Operation;
+use Composer\Package\AliasPackage;
 use Composer\Package\MemoryPackage;
+use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\PackageInterface;
+use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositoryInterface;
@@ -92,18 +95,36 @@ EOT
             $composer->getDownloadManager()->setPreferSource(true);
         }
 
+        $repoManager = $composer->getRepositoryManager();
+
         // create local repo, this contains all packages that are installed in the local project
-        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
+        $localRepo = $repoManager->getLocalRepository();
         // create installed repo, this contains all local packages + platform packages (php & extensions)
         $installedRepo = new CompositeRepository(array($localRepo, new PlatformRepository()));
         if ($additionalInstalledRepository) {
             $installedRepo->addRepository($additionalInstalledRepository);
         }
 
+        // prepare aliased packages
+        if (!$update && $composer->getLocker()->isLocked()) {
+            $aliases = $composer->getLocker()->getAliases();
+        } else {
+            $aliases = $composer->getPackage()->getAliases();
+        }
+        foreach ($aliases as $alias) {
+            foreach ($repoManager->findPackages($alias['package'], $alias['version']) as $package) {
+                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias']));
+            }
+            foreach ($repoManager->getLocalRepository()->findPackages($alias['package'], $alias['version']) as $package) {
+                $repoManager->getLocalRepository()->addPackage(new AliasPackage($package, $alias['alias']));
+                $repoManager->getLocalRepository()->removePackage($package);
+            }
+        }
+
         // creating repository pool
         $pool = new Pool;
         $pool->addRepository($installedRepo);
-        foreach ($composer->getRepositoryManager()->getRepositories() as $repository) {
+        foreach ($repoManager->getRepositories() as $repository) {
             $pool->addRepository($repository);
         }
 
@@ -118,11 +139,11 @@ EOT
         $request = new Request($pool);
         if ($update) {
             $io->write('<info>Updating dependencies</info>');
-            $installedPackages = $installedRepo->getPackages();
-            $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
 
             $request->updateAll();
 
+            $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
+
             foreach ($links as $link) {
                 $request->install($link->getTarget(), $link->getConstraint());
             }
@@ -135,7 +156,14 @@ EOT
             }
 
             foreach ($composer->getLocker()->getLockedPackages() as $package) {
-                $constraint = new VersionConstraint('=', $package->getVersion());
+                $version = $package->getVersion();
+                foreach ($aliases as $alias) {
+                    if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
+                        $version = $alias['alias'];
+                        break;
+                    }
+                }
+                $constraint = new VersionConstraint('=', $version);
                 $request->install($package->getName(), $constraint);
             }
         } else {
@@ -156,14 +184,13 @@ EOT
         // solve dependencies
         $operations = $solver->solve($request);
 
-        // execute operations
-        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) {
+                if ($package instanceof AliasPackage) {
+                    $package = $package->getAliasOf();
+                }
+
                 // skip non-dev packages
                 if (!$package->isDev()) {
                     continue;
@@ -179,13 +206,26 @@ EOT
                 }
 
                 // force update
-                $newPackage = $composer->getRepositoryManager()->findPackage($package->getName(), $package->getVersion());
+                $newPackage = $repoManager->findPackage($package->getName(), $package->getVersion());
                 if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
                     $operations[] = new UpdateOperation($package, $newPackage);
                 }
             }
         }
 
+        // anti-alias local repository to allow updates to work fine
+        foreach ($repoManager->getLocalRepository()->getPackages() as $package) {
+            if ($package instanceof AliasPackage) {
+                $repoManager->getLocalRepository()->addPackage(clone $package->getAliasOf());
+                $repoManager->getLocalRepository()->removePackage($package);
+            }
+        }
+
+        // execute operations
+        if (!$operations) {
+            $io->write('<info>Nothing to install/update</info>');
+        }
+
         foreach ($operations as $operation) {
             if ($verbose) {
                 $io->write((string) $operation);
@@ -219,7 +259,7 @@ EOT
 
         if (!$dryRun) {
             if ($update || !$composer->getLocker()->isLocked()) {
-                $composer->getLocker()->lockPackages($localRepo->getPackages());
+                $composer->getLocker()->setLockData($localRepo->getPackages(), $aliases);
                 $io->write('<info>Writing lock file</info>');
             }
 

+ 2 - 2
src/Composer/Command/ShowCommand.php

@@ -118,7 +118,7 @@ EOT
 
         // we only have a name, so search for the highest version of the given package
         $highestVersion = null;
-        foreach ($repos->findPackagesByName($input->getArgument('package')) as $package) {
+        foreach ($repos->findPackages($input->getArgument('package')) as $package) {
             if (null === $highestVersion || version_compare($package->getVersion(), $highestVersion->getVersion(), '>=')) {
                 $highestVersion = $package;
             }
@@ -164,7 +164,7 @@ EOT
 
         $versions = array();
 
-        foreach ($repos->findPackagesByName($package->getName()) as $version) {
+        foreach ($repos->findPackages($package->getName()) as $version) {
             $versions[] = $version->getPrettyVersion();
         }
 

+ 11 - 0
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -14,6 +14,7 @@ namespace Composer\DependencyResolver;
 
 use Composer\Repository\RepositoryInterface;
 use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
 use Composer\Package\LinkConstraint\VersionConstraint;
 
 /**
@@ -103,6 +104,16 @@ class DefaultPolicy implements PolicyInterface
     public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $ignoreReplace = false)
     {
         if ($a->getRepository() === $b->getRepository()) {
+            if ($a->getName() === $b->getName()) {
+                $aAliased = $a instanceof AliasPackage;
+                $bAliased = $b instanceof AliasPackage;
+                if ($aAliased && !$bAliased) {
+                    return -1; // use a
+                }
+                if (!$aAliased && $bAliased) {
+                    return 1; // use b
+                }
+            }
 
             if (!$ignoreReplace) {
                 // return original, not replaced

+ 2 - 2
src/Composer/DependencyResolver/Solver.php

@@ -1393,7 +1393,7 @@ class Solver
 
         // if there are multiple candidates, then branch
         if (count($literals)) {
-            $this->branches[] = array($literals, -$level);
+            $this->branches[] = array($literals, $level);
         }
 
         return $this->setPropagateLearn($level, $selectedLiteral, $disableRules, $rule);
@@ -1996,7 +1996,7 @@ class Solver
                     $level = $lastLevel;
                     $this->revert($level);
 
-                    $why = $this->decisionQueueWhy[count($this->decisionQueueWhy)];
+                    $why = $this->decisionQueueWhy[count($this->decisionQueueWhy) - 1];
 
                     $oLevel = $level;
                     $level = $this->setPropagateLearn($level, $lastLiteral, $disableRules, $why);

+ 13 - 3
src/Composer/Downloader/GitDownloader.php

@@ -29,7 +29,10 @@ class GitDownloader extends VcsDownloader
         $ref = escapeshellarg($package->getSourceReference());
         $path = escapeshellarg($path);
         $this->io->write("    Cloning ".$package->getSourceReference());
-        $this->process->execute(sprintf('git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s', $url, $path, $ref), $ignoredOutput);
+        $command = sprintf('git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s', $url, $path, $ref);
+        if (0 !== $this->process->execute($command, $ignoredOutput)) {
+            throw new \RuntimeException('Failed to execute ' . $command);
+        }
     }
 
     /**
@@ -40,7 +43,10 @@ class GitDownloader extends VcsDownloader
         $ref = escapeshellarg($target->getSourceReference());
         $path = escapeshellarg($path);
         $this->io->write("    Checking out ".$target->getSourceReference());
-        $this->process->execute(sprintf('cd %s && git fetch && git checkout %2$s && git reset --hard %2$s', $path, $ref), $ignoredOutput);
+        $command = sprintf('cd %s && git fetch && git checkout %2$s && git reset --hard %2$s', $path, $ref);
+        if (0 !== $this->process->execute($command, $ignoredOutput)) {
+            throw new \RuntimeException('Failed to execute ' . $command);
+        }
     }
 
     /**
@@ -48,7 +54,11 @@ class GitDownloader extends VcsDownloader
      */
     protected function enforceCleanDirectory($path)
     {
-        $this->process->execute(sprintf('cd %s && git status --porcelain', escapeshellarg($path)), $output);
+        $command = sprintf('cd %s && git status --porcelain', escapeshellarg($path));
+        if (0 !== $this->process->execute($command, $output)) {
+            throw new \RuntimeException('Failed to execute ' . $command);
+        }
+
         if (trim($output)) {
             throw new \RuntimeException('Source directory has uncommitted changes');
         }

+ 6 - 0
src/Composer/Factory.php

@@ -15,6 +15,7 @@ namespace Composer;
 use Composer\Json\JsonFile;
 use Composer\IO\IOInterface;
 use Composer\Repository\RepositoryManager;
+use Composer\Util\ProcessExecutor;
 
 /**
  * Creates an configured instance of composer.
@@ -67,6 +68,11 @@ class Factory
         }
         $binDir = getenv('COMPOSER_BIN_DIR') ?: $packageConfig['config']['bin-dir'];
 
+        // setup process timeout
+        if (false !== getenv('COMPOSER_PROCESS_TIMEOUT')) {
+            ProcessExecutor::setTimeout((int) getenv('COMPOSER_PROCESS_TIMEOUT'));
+        }
+
         // initialize repository manager
         $rm = $this->createRepositoryManager($io);
 

+ 19 - 4
src/Composer/Installer/InstallationManager.php

@@ -13,6 +13,7 @@
 namespace Composer\Installer;
 
 use Composer\Package\PackageInterface;
+use Composer\Package\AliasPackage;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\DependencyResolver\Operation\InstallOperation;
 use Composer\DependencyResolver\Operation\UpdateOperation;
@@ -123,8 +124,12 @@ class InstallationManager
      */
     public function install(InstallOperation $operation)
     {
-        $installer = $this->getInstaller($operation->getPackage()->getType());
-        $installer->install($operation->getPackage());
+        $package = $operation->getPackage();
+        if ($package instanceof AliasPackage) {
+            $package = $package->getAliasOf();
+        }
+        $installer = $this->getInstaller($package->getType());
+        $installer->install($package);
     }
 
     /**
@@ -135,7 +140,13 @@ class InstallationManager
     public function update(UpdateOperation $operation)
     {
         $initial = $operation->getInitialPackage();
+        if ($initial instanceof AliasPackage) {
+            $initial = $initial->getAliasOf();
+        }
         $target  = $operation->getTargetPackage();
+        if ($target instanceof AliasPackage) {
+            $target = $target->getAliasOf();
+        }
 
         $initialType = $initial->getType();
         $targetType  = $target->getType();
@@ -156,8 +167,12 @@ class InstallationManager
      */
     public function uninstall(UninstallOperation $operation)
     {
-        $installer = $this->getInstaller($operation->getPackage()->getType());
-        $installer->uninstall($operation->getPackage());
+        $package = $operation->getPackage();
+        if ($package instanceof AliasPackage) {
+            $package = $package->getAliasOf();
+        }
+        $installer = $this->getInstaller($package->getType());
+        $installer->uninstall($package);
     }
 
     /**

+ 236 - 0
src/Composer/Package/AliasPackage.php

@@ -0,0 +1,236 @@
+<?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\Package;
+
+use Composer\Package\LinkConstraint\LinkConstraintInterface;
+use Composer\Package\LinkConstraint\VersionConstraint;
+use Composer\Repository\RepositoryInterface;
+use Composer\Repository\PlatformRepository;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class AliasPackage extends BasePackage
+{
+    protected $version;
+    protected $dev;
+    protected $aliasOf;
+
+    protected $requires;
+    protected $conflicts;
+    protected $provides;
+    protected $replaces;
+    protected $recommends;
+    protected $suggests;
+
+    /**
+     * All descendants' constructors should call this parent constructor
+     *
+     * @param PackageInterface $aliasOf The package this package is an alias of
+     * @param string $version The version the alias must report
+     */
+    public function __construct($aliasOf, $version)
+    {
+        parent::__construct($aliasOf->getName());
+
+        $this->version = $version;
+        $this->aliasOf = $aliasOf;
+        $this->dev = 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
+
+        foreach (self::$supportedLinkTypes as $type => $description) {
+            $links = $aliasOf->{'get'.ucfirst($description)}();
+            $newLinks = array();
+            foreach ($links as $link) {
+                // link is self.version, but must be replacing also the replaced version
+                if ('self.version' === $link->getPrettyConstraint()) {
+                    $newLinks[] = new Link($link->getSource(), $link->getTarget(), new VersionConstraint('=', $this->version), $description, $this->version);
+                }
+            }
+            $this->$description = array_merge($links, $newLinks);
+        }
+    }
+
+    public function getAliasOf()
+    {
+        return $this->aliasOf;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getPrettyVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isDev()
+    {
+        return $this->dev;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getRequires()
+    {
+        return $this->requires;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getConflicts()
+    {
+        return $this->conflicts;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getProvides()
+    {
+        return $this->provides;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getReplaces()
+    {
+        return $this->replaces;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getRecommends()
+    {
+        return $this->recommends;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    function getSuggests()
+    {
+        return $this->suggests;
+    }
+
+    /***************************************
+     * Wrappers around the aliased package *
+     ***************************************/
+
+    public function getType()
+    {
+        return $this->aliasOf->getType();
+    }
+    public function getTargetDir()
+    {
+        return $this->aliasOf->getTargetDir();
+    }
+    public function getExtra()
+    {
+        return $this->aliasOf->getExtra();
+    }
+    public function setInstallationSource($type)
+    {
+        $this->aliasOf->setInstallationSource($type);
+    }
+    public function getInstallationSource()
+    {
+        return $this->aliasOf->getInstallationSource();
+    }
+    public function getSourceType()
+    {
+        return $this->aliasOf->getSourceType();
+    }
+    public function getSourceUrl()
+    {
+        return $this->aliasOf->getSourceUrl();
+    }
+    public function getSourceReference()
+    {
+        return $this->aliasOf->getSourceReference();
+    }
+    public function getDistType()
+    {
+        return $this->aliasOf->getDistType();
+    }
+    public function getDistUrl()
+    {
+        return $this->aliasOf->getDistUrl();
+    }
+    public function getDistReference()
+    {
+        return $this->aliasOf->getDistReference();
+    }
+    public function getDistSha1Checksum()
+    {
+        return $this->aliasOf->getDistSha1Checksum();
+    }
+    public function getScripts()
+    {
+        return $this->aliasOf->getScripts();
+    }
+    public function getLicense()
+    {
+        return $this->aliasOf->getLicense();
+    }
+    public function getAutoload()
+    {
+        return $this->aliasOf->getAutoload();
+    }
+    public function getRepositories()
+    {
+        return $this->aliasOf->getRepositories();
+    }
+    public function getReleaseDate()
+    {
+        return $this->aliasOf->getReleaseDate();
+    }
+    public function getBinaries()
+    {
+        return $this->aliasOf->getBinaries();
+    }
+    public function getKeywords()
+    {
+        return $this->aliasOf->getKeywords();
+    }
+    public function getDescription()
+    {
+        return $this->aliasOf->getDescription();
+    }
+    public function getHomepage()
+    {
+        return $this->aliasOf->getHomepage();
+    }
+    public function getAuthors()
+    {
+        return $this->aliasOf->getAuthors();
+    }
+    public function __toString()
+    {
+        return $this->aliasOf->__toString();
+    }
+}

+ 10 - 0
src/Composer/Package/BasePackage.php

@@ -24,8 +24,18 @@ use Composer\Repository\PlatformRepository;
  */
 abstract class BasePackage implements PackageInterface
 {
+    public static $supportedLinkTypes = array(
+        'require'   => 'requires',
+        'conflict'  => 'conflicts',
+        'provide'   => 'provides',
+        'replace'   => 'replaces',
+        'recommend' => 'recommends',
+        'suggest'   => 'suggests',
+    );
+
     protected $name;
     protected $prettyName;
+
     protected $repository;
     protected $id;
 

+ 7 - 13
src/Composer/Package/Loader/ArrayLoader.php

@@ -22,15 +22,6 @@ use Composer\Repository\RepositoryManager;
  */
 class ArrayLoader
 {
-    protected $supportedLinkTypes = array(
-        'require'   => 'requires',
-        'conflict'  => 'conflicts',
-        'provide'   => 'provides',
-        'replace'   => 'replaces',
-        'recommend' => 'recommends',
-        'suggest'   => 'suggests',
-    );
-
     protected $versionParser;
 
     public function __construct(VersionParser $parser = null)
@@ -99,7 +90,9 @@ class ArrayLoader
 
         if (!empty($config['time'])) {
             try {
-                $package->setReleaseDate(new \DateTime($config['time']));
+                $date = new \DateTime($config['time']);
+                $date->setTimezone(new \DateTimeZone('UTC'));
+                $package->setReleaseDate($date);
             } catch (\Exception $e) {
             }
         }
@@ -141,7 +134,7 @@ class ArrayLoader
             $package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $config['dist']['shasum'] : null);
         }
 
-        foreach ($this->supportedLinkTypes as $type => $description) {
+        foreach (Package\BasePackage::$supportedLinkTypes as $type => $description) {
             if (isset($config[$type])) {
                 $method = 'set'.ucfirst($description);
                 $package->{$method}(
@@ -162,9 +155,10 @@ class ArrayLoader
         $links = array();
         foreach ($linksSpecs as $packageName => $constraint) {
             if ('self.version' === $constraint) {
-                $constraint = $package->getPrettyVersion();
+                $parsedConstraint = $this->versionParser->parseConstraints($package->getPrettyVersion());
+            } else {
+                $parsedConstraint = $this->versionParser->parseConstraints($constraint);
             }
-            $parsedConstraint = $this->versionParser->parseConstraints($constraint);
             $links[] = new Package\Link($package->getName(), $packageName, $parsedConstraint, $description, $constraint);
         }
 

+ 15 - 0
src/Composer/Package/Loader/RootPackageLoader.php

@@ -43,6 +43,21 @@ class RootPackageLoader extends ArrayLoader
 
         $package = parent::load($config);
 
+        if (isset($config['require'])) {
+            $aliases = array();
+            foreach ($config['require'] as $reqName => $reqVersion) {
+                if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $reqVersion, $match)) {
+                    $aliases[] = array(
+                        'package' => strtolower($reqName),
+                        'version' => $this->versionParser->normalize($match[1]),
+                        'alias' => $this->versionParser->normalize($match[2]),
+                    );
+                }
+            }
+
+            $package->setAliases($aliases);
+        }
+
         if (isset($config['repositories'])) {
             foreach ($config['repositories'] as $index => $repo) {
                 if (isset($repo['packagist']) && $repo['packagist'] === false) {

+ 10 - 2
src/Composer/Package/Locker.php

@@ -91,6 +91,12 @@ class Locker
         return $packages;
     }
 
+    public function getAliases()
+    {
+        $lockList = $this->getLockData();
+        return isset($lockList['aliases']) ? $lockList['aliases'] : array();
+    }
+
     public function getLockData()
     {
         if (!$this->isLocked()) {
@@ -101,15 +107,17 @@ class Locker
     }
 
     /**
-     * Locks provided packages into lockfile.
+     * Locks provided data into lockfile.
      *
      * @param array $packages array of packages
+     * @param array $aliases array of aliases
      */
-    public function lockPackages(array $packages)
+    public function setLockData(array $packages, array $aliases)
     {
         $lock = array(
             'hash' => $this->hash,
             'packages' => array(),
+            'aliases' => $aliases,
         );
         foreach ($packages as $package) {
             $name    = $package->getPrettyName();

+ 17 - 0
src/Composer/Package/MemoryPackage.php

@@ -41,6 +41,7 @@ class MemoryPackage extends BasePackage
     protected $extra = array();
     protected $binaries = array();
     protected $scripts = array();
+    protected $aliases = array();
     protected $dev;
 
     protected $requires = array();
@@ -156,6 +157,22 @@ class MemoryPackage extends BasePackage
         return $this->scripts;
     }
 
+    /**
+     * @param array $aliases
+     */
+    public function setAliases(array $aliases)
+    {
+        $this->aliases = $aliases;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getAliases()
+    {
+        return $this->aliases;
+    }
+
     /**
      * {@inheritDoc}
      */

+ 7 - 0
src/Composer/Package/PackageInterface.php

@@ -293,6 +293,13 @@ interface PackageInterface
      */
     function getDescription();
 
+    /**
+     * Returns the package binaries
+     *
+     * @return string
+     */
+    function getBinaries();
+
     /**
      * Returns the package homepage
      *

+ 5 - 0
src/Composer/Package/Version/VersionParser.php

@@ -34,6 +34,11 @@ class VersionParser
     {
         $version = trim($version);
 
+        // ignore aliases and just assume the alias is required instead of the source
+        if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) {
+            $version = $match[2];
+        }
+
         // match master-like branches
         if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
             return '9999999-dev';

+ 9 - 2
src/Composer/Repository/ArrayRepository.php

@@ -44,14 +44,21 @@ class ArrayRepository implements RepositoryInterface
     /**
      * {@inheritDoc}
      */
-    public function findPackagesByName($name)
+    public function findPackages($name, $version = null)
     {
         // normalize name
         $name = strtolower($name);
+
+        // normalize version
+        if (null !== $version) {
+            $versionParser = new VersionParser();
+            $version = $versionParser->normalize($version);
+        }
+
         $packages = array();
 
         foreach ($this->getPackages() as $package) {
-            if ($package->getName() === $name) {
+            if ($package->getName() === $name && (null === $version || $version === $package->getVersion())) {
                 $packages[] = $package;
             }
         }

+ 2 - 2
src/Composer/Repository/CompositeRepository.php

@@ -68,12 +68,12 @@ class CompositeRepository implements RepositoryInterface
     /**
      * {@inheritdoc}
      */
-    public function findPackagesByName($name)
+    public function findPackages($name, $version = null)
     {
         $packages = array();
         foreach ($this->repositories as $repository) {
             /* @var $repository RepositoryInterface */
-            $packages[] = $repository->findPackagesByName($name);
+            $packages[] = $repository->findPackages($name, $version);
         }
         return call_user_func_array('array_merge', $packages);
     }

+ 4 - 3
src/Composer/Repository/RepositoryInterface.php

@@ -32,7 +32,7 @@ interface RepositoryInterface extends \Countable
     function hasPackage(PackageInterface $package);
 
     /**
-     * Searches for a package by it's name and version (if has one).
+     * Searches for the first match of a package by name and version.
      *
      * @param   string  $name       package name
      * @param   string  $version    package version
@@ -42,13 +42,14 @@ interface RepositoryInterface extends \Countable
     function findPackage($name, $version);
 
     /**
-     * Searches for packages by it's name.
+     * Searches for all packages matching a name and optionally a version.
      *
      * @param   string  $name       package name
+     * @param   string  $version    package version
      *
      * @return  array
      */
-    function findPackagesByName($name);
+    function findPackages($name, $version = null);
 
     /**
      * Returns list of registered packages.

+ 19 - 0
src/Composer/Repository/RepositoryManager.php

@@ -50,6 +50,25 @@ class RepositoryManager
         }
     }
 
+    /**
+     * Searches for all packages matching a name and optionally a version in managed repositories.
+     *
+     * @param   string  $name       package name
+     * @param   string  $version    package version
+     *
+     * @return  array
+     */
+    public function findPackages($name, $version)
+    {
+        $packages = array();
+
+        foreach ($this->repositories as $repository) {
+            $packages = array_merge($packages, $repository->findPackages($name, $version));
+        }
+
+        return $packages;
+    }
+
     /**
      * Adds repository
      *

+ 1 - 0
src/Composer/Repository/Vcs/HgDriver.php

@@ -130,6 +130,7 @@ class HgDriver extends VcsDriver implements VcsDriverInterface
                     $tags[$match[1]] = $match[2];
                 }
             }
+            unset($tags['tip']);
 
             $this->tags = $tags;
         }

+ 14 - 2
src/Composer/Util/ProcessExecutor.php

@@ -19,6 +19,8 @@ use Symfony\Component\Process\Process;
  */
 class ProcessExecutor
 {
+    static protected $timeout = 60;
+
     /**
      * runs a process on the commandline
      *
@@ -29,7 +31,7 @@ class ProcessExecutor
     public function execute($command, &$output = null)
     {
         $captureOutput = count(func_get_args()) > 1;
-        $process = new Process($command);
+        $process = new Process($command, null, null, null, static::getTimeout());
         $process->run(function($type, $buffer) use ($captureOutput) {
             if ($captureOutput) {
                 return;
@@ -49,4 +51,14 @@ class ProcessExecutor
     {
         return ((string) $output === '') ? array() : preg_split('{\r?\n}', $output);
     }
-}
+
+    static public function getTimeout()
+    {
+        return static::$timeout;
+    }
+
+    static public function setTimeout($timeout)
+    {
+        static::$timeout = $timeout;
+    }
+}

+ 60 - 4
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -43,7 +43,31 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->once())
             ->method('execute')
-            ->with($this->equalTo($expectedGitCommand));
+            ->with($this->equalTo($expectedGitCommand))
+            ->will($this->returnValue(0));
+
+        $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor);
+        $downloader->download($packageMock, 'composerPath');
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
+    {
+        $expectedGitCommand = $this->getCmd('git clone \'https://github.com/l3l0/composer\' \'composerPath\' && cd \'composerPath\' && git checkout \'ref\' && git reset --hard \'ref\'');
+        $packageMock = $this->getMock('Composer\Package\PackageInterface');
+        $packageMock->expects($this->any())
+            ->method('getSourceReference')
+            ->will($this->returnValue('ref'));
+        $packageMock->expects($this->once())
+            ->method('getSourceUrl')
+            ->will($this->returnValue('https://github.com/l3l0/composer'));
+        $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+        $processExecutor->expects($this->once())
+            ->method('execute')
+            ->with($this->equalTo($expectedGitCommand))
+            ->will($this->returnValue(1));
 
         $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor);
         $downloader->download($packageMock, 'composerPath');
@@ -79,10 +103,41 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->at(0))
             ->method('execute')
-            ->with($this->equalTo($expectedGitResetCommand));
+            ->with($this->equalTo($expectedGitResetCommand))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(1))
+            ->method('execute')
+            ->with($this->equalTo($expectedGitUpdateCommand))
+            ->will($this->returnValue(0));
+
+        $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor);
+        $downloader->update($packageMock, $packageMock, 'composerPath');
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
+    {
+        $expectedGitUpdateCommand = $this->getCmd('cd \'composerPath\' && git fetch && git checkout \'ref\' && git reset --hard \'ref\'');
+        $expectedGitResetCommand = $this->getCmd('cd \'composerPath\' && git status --porcelain');
+
+        $packageMock = $this->getMock('Composer\Package\PackageInterface');
+        $packageMock->expects($this->any())
+            ->method('getSourceReference')
+            ->will($this->returnValue('ref'));
+        $packageMock->expects($this->any())
+            ->method('getSourceUrl')
+            ->will($this->returnValue('https://github.com/l3l0/composer'));
+        $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
+        $processExecutor->expects($this->at(0))
+            ->method('execute')
+            ->with($this->equalTo($expectedGitResetCommand))
+            ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
             ->method('execute')
-            ->with($this->equalTo($expectedGitUpdateCommand));
+            ->with($this->equalTo($expectedGitUpdateCommand))
+            ->will($this->returnValue(1));
 
         $downloader = new GitDownloader($this->getMock('Composer\IO\IOInterface'), $processExecutor);
         $downloader->update($packageMock, $packageMock, 'composerPath');
@@ -96,7 +151,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor->expects($this->any())
             ->method('execute')
-            ->with($this->equalTo($expectedGitResetCommand));
+            ->with($this->equalTo($expectedGitResetCommand))
+            ->will($this->returnValue(0));
         $filesystem = $this->getMock('Composer\Util\Filesystem');
         $filesystem->expects($this->any())
             ->method('removeDirectory')

+ 4 - 3
tests/Composer/Test/Package/LockerTest.php

@@ -114,7 +114,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
         $locker->getLockedPackages();
     }
 
-    public function testLockPackages()
+    public function testSetLockData()
     {
         $json = $this->createJsonFileMock();
         $repo = $this->createRepositoryManagerMock();
@@ -151,9 +151,10 @@ class LockerTest extends \PHPUnit_Framework_TestCase
                     array('package' => 'pkg1', 'version' => '1.0.0-beta'),
                     array('package' => 'pkg2', 'version' => '0.1.10')
                 ),
+                'aliases' => array(),
             ));
 
-        $locker->lockPackages(array($package1, $package2));
+        $locker->setLockData(array($package1, $package2), array());
     }
 
     public function testLockBadPackages()
@@ -171,7 +172,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
         $this->setExpectedException('LogicException');
 
-        $locker->lockPackages(array($package1));
+        $locker->setLockData(array($package1), array());
     }
 
     public function testIsFresh()

+ 2 - 0
tests/Composer/Test/Package/Version/VersionParserTest.php

@@ -53,6 +53,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             'parses trunk'      => array('dev-trunk',           '9999999-dev'),
             'parses arbitrary'  => array('dev-feature-foo',     'dev-feature-foo'),
             'parses arbitrary2' => array('DEV-FOOBAR',          'dev-foobar'),
+            'ignores aliases'   => array('dev-master as 1.0.0', '1.0.0.0'),
         );
     }
 
@@ -128,6 +129,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             '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')),
+            'ignores aliases'   => array('dev-master as 1.0.0', new VersionConstraint('=', '1.0.0.0')),
         );
     }
 

+ 3 - 3
tests/Composer/Test/Repository/ArrayRepositoryTest.php

@@ -51,18 +51,18 @@ class ArrayRepositoryTest extends TestCase
         $this->assertFalse($repo->hasPackage($this->getPackage('bar', '1')));
     }
 
-    public function testFindPackagesByName()
+    public function testFindPackages()
     {
         $repo = new ArrayRepository();
         $repo->addPackage($this->getPackage('foo', '1'));
         $repo->addPackage($this->getPackage('bar', '2'));
         $repo->addPackage($this->getPackage('bar', '3'));
 
-        $foo = $repo->findPackagesByName('foo');
+        $foo = $repo->findPackages('foo');
         $this->assertCount(1, $foo);
         $this->assertEquals('foo', $foo[0]->getName());
 
-        $bar = $repo->findPackagesByName('bar');
+        $bar = $repo->findPackages('bar');
         $this->assertCount(2, $bar);
         $this->assertEquals('bar', $bar[0]->getName());
     }

+ 24 - 24
tests/Composer/Test/Repository/CompositeRepositoryTest.php

@@ -22,15 +22,15 @@ class CompositeRepositoryTest extends TestCase
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
-        
+
         $arrayRepoTwo = new ArrayRepository;
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
-        
+
         $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo));
-        
+
         $this->assertTrue($repo->hasPackage($this->getPackage('foo', '1')), "Should have package 'foo/1'");
         $this->assertTrue($repo->hasPackage($this->getPackage('bar', '1')), "Should have package 'bar/1'");
-        
+
         $this->assertFalse($repo->hasPackage($this->getPackage('foo', '2')), "Should not have package 'foo/2'");
         $this->assertFalse($repo->hasPackage($this->getPackage('bar', '2')), "Should not have package 'bar/2'");
     }
@@ -39,12 +39,12 @@ class CompositeRepositoryTest extends TestCase
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
-    
+
         $arrayRepoTwo = new ArrayRepository;
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
-    
+
         $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo));
-    
+
         $this->assertEquals('foo', $repo->findPackage('foo', '1')->getName(), "Should find package 'foo/1' and get name of 'foo'");
         $this->assertEquals('1', $repo->findPackage('foo', '1')->getPrettyVersion(), "Should find package 'foo/1' and get pretty version of '1'");
         $this->assertEquals('bar', $repo->findPackage('bar', '1')->getName(), "Should find package 'bar/1' and get name of 'bar'");
@@ -52,7 +52,7 @@ class CompositeRepositoryTest extends TestCase
         $this->assertNull($repo->findPackage('foo', '2'), "Should not find package 'foo/2'");
     }
 
-    public function testFindPackagesByName()
+    public function testFindPackages()
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
@@ -63,32 +63,32 @@ class CompositeRepositoryTest extends TestCase
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
         $arrayRepoTwo->addPackage($this->getPackage('bar', '2'));
         $arrayRepoTwo->addPackage($this->getPackage('foo', '3'));
-        
+
         $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo));
-        
-        $bats = $repo->findPackagesByName('bat');
+
+        $bats = $repo->findPackages('bat');
         $this->assertCount(1, $bats, "Should find one instance of 'bats' (defined in just one repository)");
         $this->assertEquals('bat', $bats[0]->getName(), "Should find packages named 'bat'");
 
-        $bars = $repo->findPackagesByName('bar');
+        $bars = $repo->findPackages('bar');
         $this->assertCount(2, $bars, "Should find two instances of 'bar' (both defined in the same repository)");
         $this->assertEquals('bar', $bars[0]->getName(), "Should find packages named 'bar'");
-        
-        $foos = $repo->findPackagesByName('foo');
+
+        $foos = $repo->findPackages('foo');
         $this->assertCount(3, $foos, "Should find three instances of 'foo' (two defined in one repository, the third in the other)");
         $this->assertEquals('foo', $foos[0]->getName(), "Should find packages named 'foo'");
     }
-    
+
     public function testGetPackages()
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
-    
+
         $arrayRepoTwo = new ArrayRepository;
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
-    
+
         $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo));
-        
+
         $packages = $repo->getPackages();
         $this->assertCount(2, $packages, "Should get two packages");
         $this->assertEquals("foo", $packages[0]->getName(), "First package should have name of 'foo'");
@@ -96,17 +96,17 @@ class CompositeRepositoryTest extends TestCase
         $this->assertEquals("bar", $packages[1]->getName(), "Second package should have name of 'bar'");
         $this->assertEquals("1", $packages[1]->getPrettyVersion(), "Second package should have pretty version of '1'");
     }
-    
+
     public function testAddRepository()
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
-        
+
         $arrayRepoTwo = new ArrayRepository;
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
         $arrayRepoTwo->addPackage($this->getPackage('bar', '2'));
         $arrayRepoTwo->addPackage($this->getPackage('bar', '3'));
-        
+
         $repo = new CompositeRepository(array($arrayRepoOne));
         $this->assertCount(1, $repo, "Composite repository should have just one package before addRepository() is called");
         $repo->addRepository($arrayRepoTwo);
@@ -117,12 +117,12 @@ class CompositeRepositoryTest extends TestCase
     {
         $arrayRepoOne = new ArrayRepository;
         $arrayRepoOne->addPackage($this->getPackage('foo', '1'));
-    
+
         $arrayRepoTwo = new ArrayRepository;
         $arrayRepoTwo->addPackage($this->getPackage('bar', '1'));
-        
+
         $repo = new CompositeRepository(array($arrayRepoOne, $arrayRepoTwo));
-    
+
         $this->assertEquals(2, count($repo), "Should return '2' for count(\$repo)");
     }
 }

+ 8 - 0
tests/Composer/Test/Util/ProcessExecutorTest.php

@@ -33,6 +33,14 @@ class ProcessExecutorTest extends TestCase
         $this->assertEquals("foo".PHP_EOL, $output);
     }
 
+    public function testTimeout()
+    {
+        ProcessExecutor::setTimeout(1);
+        $process = new ProcessExecutor;
+        $this->assertEquals(1, $process->getTimeout());
+        ProcessExecutor::setTimeout(60);
+    }
+
     public function testSplitLines()
     {
         $process = new ProcessExecutor;