diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a726272..985127bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,14 @@ * Added `create-project` command to install a project from scratch with composer * Added automated `classmap` autoloading support for non-PSR-0 compliant projects - * Git clones from GitHub automatically select between git/https/http protocols - * Enhanced `validate` command to give more feedback + * Improved clones from GitHub which now automatically select between git/https/http protocols + * Added support for private GitHub repositories (use --no-interaction for CI) + * Improved `validate` command to give more feedback * Added "file" downloader type to download plain files * Added support for authentication with svn repositories - * Dependency on filter_var is now optional - * Various robustness & error handling improvements + * Removed dependency on filter_var + * Improved the `search` & `show` commands output + * Various robustness & error handling improvements, docs fixes and more * 1.0.0-alpha1 (2012-03-01) diff --git a/bin/compile b/bin/compile index bd8426693..3bb0fb73d 100755 --- a/bin/compile +++ b/bin/compile @@ -1,11 +1,7 @@ #!/usr/bin/env php **Note:** You can also perform the checks only without downloading composer +> **Note:** You can also perform the checks only without downloading Composer > by using the `--check` option. For more information, just use `--help`. > > $ curl -s http://getcomposer.org/installer | php -- --help -## Project setup +## `composer.json`: Project Setup -To start using composer in your project, all you need is a `composer.json` +To start using Composer in your project, all you need is a `composer.json` file. This file describes the dependencies of your project and may contain other metadata as well. The [JSON format](http://json.org/) is quite easy to write. It allows you to define nested structures. +### The `require` Key + The first (and often only) thing you specify in `composer.json` is the -`require` key. You're simply telling composer which packages your project +`require` key. You're simply telling Composer which packages your project depends on. { @@ -53,12 +40,13 @@ depends on. } } -As you can see, `require` takes an object that maps package names to versions. +As you can see, `require` takes an object that maps **package names** (e.g. `monolog/monolog`) +to **package versions** (e.g. `1.0.*`). -## Package names +### Package Names The package name consists of a vendor name and the project's name. Often these -will be identical. The vendor name exists to prevent naming clashes. It allows +will be identical - the vendor name just exists to prevent naming clashes. It allows two different people to create a library named `json`, which would then just be named `igorw/json` and `seldaek/json`. @@ -68,10 +56,10 @@ allows adding more related projects under the same namespace later on. If you are maintaining a library, this would make it really easy to split it up into smaller decoupled parts. -## Package versions +### Package Versions -We are also requiring the version `1.0.*` of monolog. This means any version -in the `1.0` development branch. It would match `1.0.0`, `1.0.2` and `1.0.20`. +We are requiring version `1.0.*` of monolog. This means any version in the `1.0` +development branch. It would match `1.0.0`, `1.0.2` or `1.0.20`. Version constraints can be specified in a few different ways. @@ -80,14 +68,14 @@ Version constraints can be specified in a few different ways. * **Range:** By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`. An example range would be - `>=1.0`. You can define multiple of these, separated by comma: `>=1.0,<2.0`. + `>=1.0`. You can define multiple ranges, separated by a comma: `>=1.0,<2.0`. * **Wildcard:** You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1-dev`. -## Installing dependencies +## Installing Dependencies -To fetch the defined dependencies into the local project, you simply run the +To fetch the defined dependencies into your local project, just run the `install` command of `composer.phar`. $ php composer.phar install @@ -104,29 +92,33 @@ In case of monolog it will put it into `vendor/monolog/monolog`. Another thing that the `install` command does is it adds a `composer.lock` file into your project root. -## Lock file +## `composer.lock` - The Lock File -After installing the dependencies, composer writes the list of the exact +After installing the dependencies, Composer writes the list of the exact versions it installed into a `composer.lock` file. This locks the project to those specific versions. -**Commit your project's `composer.lock` into version control.** +**Commit your project's `composer.lock` (along with `composer.json`) into version control.** + +This is important because the `install` command checks if a lock file is present, +and if it is, it downloads the versions specified there (regardless of what `composer.json` +says). This means that anyone who sets up the project will download the exact +same version of the dependencies. -The reason is that anyone who sets up the project should get the same version. -The `install` command will check if a lock file is present. If it is, it will -use the versions specified there. If not, it will resolve the dependencies and -create a lock file. +If no `composer.json` lock file exists, it will read the dependencies and +versions from `composer.json` and create the lock file. -If any of the dependencies gets a new version, you can update to that version -by using the `update` command. This will fetch the latest matching versions and -also update the lock file. +This means that if any of the dependencies get a new version, you won't get the updates. +automatically. To update to the new version, use `update` command. This will fetch +the latest matching versions (according to your `composer.json` file) and also update +the lock file with the new version. $ php composer.phar update ## Packagist -[Packagist](http://packagist.org/) is the main composer repository. A composer -repository is basically a package source. A place where you can get packages +[Packagist](http://packagist.org/) is the main Composer repository. A Composer +repository is basically a package source: a place where you can get packages from. Packagist aims to be the central repository that everybody uses. This means that you can automatically `require` any package that is available there. @@ -134,20 +126,20 @@ there. If you go to the [packagist website](http://packagist.org/) (packagist.org), you can browse and search for packages. -Any open source project using composer should publish their packages on -packagist. +Any open source project using Composer should publish their packages on +packagist. A library doesn't need to be on packagist to be used by Composer, +but it makes life quite a bit simpler. ## 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 -`vendor/.composer/autoload.php` file for autoloading. You can simply include -this file and you will get autoloading for free. +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. require 'vendor/.composer/autoload.php'; -This makes it really easy to use third party code, because you only -have to add one line to `composer.json` and run `install`. For monolog, it +This makes it really easy to use third party code: For monolog, it means that we can just start using classes from it, and they will be autoloaded. diff --git a/doc/02-libraries.md b/doc/02-libraries.md index f5763b27a..da540e9a9 100644 --- a/doc/02-libraries.md +++ b/doc/02-libraries.md @@ -96,9 +96,9 @@ example we will publish the `acme/hello-world` library on GitHub under `github.com/composer/hello-world`. Now, To test installing the `acme/hello-world` package, we create a new -project locally. We will call it `acme/blog`. This blog will depend on `acme -/hello-world`, which in turn depends on `monolog/monolog`. We can accomplish -this by creating a new `blog` directory somewhere, containing a +project locally. We will call it `acme/blog`. This blog will depend on +`acme/hello-world`, which in turn depends on `monolog/monolog`. We can +accomplish this by creating a new `blog` directory somewhere, containing a `composer.json`: { diff --git a/doc/03-cli.md b/doc/03-cli.md index 8f917c8c3..0ee6a140e 100644 --- a/doc/03-cli.md +++ b/doc/03-cli.md @@ -157,7 +157,7 @@ will always take precedence over the values specified in `composer.json`. ### COMPOSER -By setting the `COMPOSER` env variable is is possible to set the filename of +By setting the `COMPOSER` env variable it is possible to set the filename of `composer.json` to something else. For example: diff --git a/doc/04-schema.md b/doc/04-schema.md index e3308630e..07626a1d9 100644 --- a/doc/04-schema.md +++ b/doc/04-schema.md @@ -13,7 +13,9 @@ can also be used to validate your `composer.json`. In fact, it is used by the The root of the package definition is a JSON object. -## name +## Properties + +### name The name of the package. It consists of vendor name and project name, separated by `/`. @@ -25,13 +27,13 @@ Examples: Required for published packages (libraries). -## description +### description A short description of the package. Usually this is just one line long. Optional but recommended. -## version +### version The version of the package. @@ -52,7 +54,7 @@ Optional if the package repository can infer the version from somewhere, such as the VCS tag name in the VCS repository. In that case it is also recommended to omit it. -## type +### type The type of the package. It defaults to `library`. @@ -74,7 +76,7 @@ order to be able to install the bundle. Only use a custom type if you need custom logic during installation. It is recommended to omit this field and have it just default to `library`. -## keywords +### keywords An array of keywords that the package is related to. These can be used for searching and filtering. @@ -89,13 +91,13 @@ Examples: Optional. -## homepage +### homepage An URL to the website of the project. Optional. -## time +### time Release date of the version. @@ -103,7 +105,7 @@ Must be in `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS` format. Optional. -## license +### license The license of the package. This can be either a string or an array of strings. @@ -122,7 +124,7 @@ The recommended notation for the most common licenses is: Optional, but it is highly recommended to supply this. -## authors +### authors The authors of the package. This is an array of objects. @@ -151,7 +153,7 @@ An example: Optional, but highly recommended. -## Link types +### Link types Each of these takes an object which maps package names to version constraints. @@ -179,7 +181,7 @@ Example: Optional. -## autoload +### autoload Autoload mapping for a PHP autoloader. @@ -211,7 +213,7 @@ Example: } } -## target-dir +### target-dir Defines the installation target. @@ -236,7 +238,7 @@ To do that, `autoload` and `target-dir` are defined as follows: Optional. -## repositories +### repositories Custom package repositories to use. @@ -297,11 +299,12 @@ Example: ] } -> **Note:** Order is significant here. Repositories added later will take -precedence. This also means that custom repositories can override packages -that exist on packagist. +> **Note:** Order is significant here. When looking for a package, Composer +will look from the first to the last repository, and pick the first match. +By default Packagist is added last which means that custom repositories can +override packages from it. -## config +### config A set of configuration options. It is only used for projects. @@ -323,7 +326,7 @@ Example: } } -## scripts +### scripts Composer allows you to hook into various parts of the installation process through the use of scripts. @@ -382,7 +385,7 @@ which gives you access to the `Composer\Composer` instance through the } } -## extra +### extra Arbitrary extra data for consumption by `scripts`. @@ -393,12 +396,12 @@ handler, you can do: Optional. -## bin +### bin A set of files that should be treated as binaries and symlinked into the `bin- dir` (from config). -See (Vendor Bins)[articles/vendor-bins.md] for more details. +See [Vendor Bins](articles/vendor-bins.md) for more details. Optional. diff --git a/src/Composer/Command/CreateProjectCommand.php b/src/Composer/Command/CreateProjectCommand.php index a859ac12a..4ad2e81d2 100644 --- a/src/Composer/Command/CreateProjectCommand.php +++ b/src/Composer/Command/CreateProjectCommand.php @@ -22,6 +22,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Composer\Json\JsonFile; /** * Install a package as new project into new directory. @@ -85,7 +86,7 @@ EOT if (null === $repositoryUrl) { $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org')); } elseif (".json" === substr($repositoryUrl, -5)) { - $sourceRepo = new FilesystemRepository($repositoryUrl); + $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl)); } elseif (0 === strpos($repositoryUrl, 'http')) { $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl)); } else { diff --git a/src/Composer/Command/InitCommand.php b/src/Composer/Command/InitCommand.php index 6f8433c23..cdd5c4e34 100644 --- a/src/Composer/Command/InitCommand.php +++ b/src/Composer/Command/InitCommand.php @@ -33,7 +33,7 @@ class InitCommand extends Command public function parseAuthorString($author) { - if (preg_match('/^(?P[- \.,a-z0-9]+) <(?P.+?)>$/i', $author, $match)) { + if (preg_match('/^(?P[- \.,\w]+) <(?P.+?)>$/u', $author, $match)) { if (!function_exists('filter_var') || $match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) { return array( 'name' => trim($match['name']), diff --git a/src/Composer/Compiler.php b/src/Composer/Compiler.php index ca4875a97..e6af59826 100644 --- a/src/Composer/Compiler.php +++ b/src/Composer/Compiler.php @@ -102,12 +102,11 @@ class Compiler { $path = str_replace(dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR, '', $file->getRealPath()); + $content = file_get_contents($file); if ($strip) { - $content = php_strip_whitespace($file); + $content = $this->stripWhitespace($content); } elseif ('LICENSE' === basename($file)) { - $content = "\n".file_get_contents($file)."\n"; - } else { - $content = file_get_contents($file); + $content = "\n".$content."\n"; } $content = str_replace('@package_version@', $this->version, $content); @@ -122,6 +121,40 @@ class Compiler $phar->addFromString('bin/composer', $content); } + /** + * Removes whitespace from a PHP source string while preserving line numbers. + * + * @param string $source A PHP string + * @return string The PHP string with the whitespace removed + */ + private function stripWhitespace($source) + { + if (!function_exists('token_get_all')) { + return $source; + } + + $output = ''; + foreach (token_get_all($source) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $output .= str_repeat("\n", substr_count($token[1], "\n")); + } elseif (T_WHITESPACE === $token[0]) { + // reduce wide spaces + $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); + // normalize newlines to \n + $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + // trim leading spaces + $whitespace = preg_replace('{\n +}', "\n", $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + private function getStub() { return <<<'EOF' diff --git a/src/Composer/DependencyResolver/Pool.php b/src/Composer/DependencyResolver/Pool.php index b5eeb26e7..24ef5427a 100644 --- a/src/Composer/DependencyResolver/Pool.php +++ b/src/Composer/DependencyResolver/Pool.php @@ -53,7 +53,7 @@ class Pool throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool."); } - return $priority; + return -$priority; } /** diff --git a/src/Composer/DependencyResolver/Solver.php b/src/Composer/DependencyResolver/Solver.php index fd39faed3..aa832b113 100644 --- a/src/Composer/DependencyResolver/Solver.php +++ b/src/Composer/DependencyResolver/Solver.php @@ -911,7 +911,7 @@ class Solver $this->installedMap[$package->getId()] = $package; } - if (version_compare(PHP_VERSION, '5.3.3', '>')) { + if (version_compare(PHP_VERSION, '5.3.4', '>=')) { $this->decisionMap = new \SplFixedArray($this->pool->getMaxId() + 1); } else { $this->decisionMap = array_fill(0, $this->pool->getMaxId() + 1, 0); diff --git a/src/Composer/Downloader/ArchiveDownloader.php b/src/Composer/Downloader/ArchiveDownloader.php index 07d418bed..8e921a3e4 100644 --- a/src/Composer/Downloader/ArchiveDownloader.php +++ b/src/Composer/Downloader/ArchiveDownloader.php @@ -15,7 +15,6 @@ namespace Composer\Downloader; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Util\Filesystem; -use Composer\Util\RemoteFilesystem; /** * Base downloader for archives diff --git a/src/Composer/Downloader/FileDownloader.php b/src/Composer/Downloader/FileDownloader.php index d3e668c93..1b440048f 100644 --- a/src/Composer/Downloader/FileDownloader.php +++ b/src/Composer/Downloader/FileDownloader.php @@ -27,15 +27,17 @@ use Composer\Util\RemoteFilesystem; class FileDownloader implements DownloaderInterface { protected $io; + protected $rfs; /** * Constructor. * * @param IOInterface $io The IO instance */ - public function __construct(IOInterface $io) + public function __construct(IOInterface $io, RemoteFilesystem $rfs = null) { $this->io = $io; + $this->rfs = $rfs ?: new RemoteFilesystem($io); } /** @@ -71,8 +73,7 @@ class FileDownloader implements DownloaderInterface $url = $this->processUrl($url); - $rfs = new RemoteFilesystem($this->io); - $rfs->copy($package->getSourceUrl(), $url, $fileName); + $this->rfs->copy($package->getSourceUrl(), $url, $fileName); $this->io->write(''); if (!file_exists($fileName)) { diff --git a/src/Composer/Downloader/GitDownloader.php b/src/Composer/Downloader/GitDownloader.php index cceb4cf8b..e6949ea7a 100644 --- a/src/Composer/Downloader/GitDownloader.php +++ b/src/Composer/Downloader/GitDownloader.php @@ -34,6 +34,13 @@ class GitDownloader extends VcsDownloader }; $this->runCommand($commandCallable, $package->getSourceUrl(), $path); + + // set push url for github projects + if (preg_match('{^(?:https?|git)://github.com/([^/]+)/([^/]+?)(?:\.git)?$}', $package->getSourceUrl(), $match)) { + $pushUrl = 'git@github.com:'.$match[1].'/'.$match[2].'.git'; + $cmd = sprintf('cd %s && git remote set-url --push origin %s', escapeshellarg($path), escapeshellarg($pushUrl)); + $this->process->execute($cmd, $ignoredOutput); + } } /** diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 3f4812927..958844930 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -78,17 +78,7 @@ class Factory $rm = $this->createRepositoryManager($io); // load default repository unless it's explicitly disabled - $loadPackagist = true; - if (isset($packageConfig['repositories'])) { - foreach ($packageConfig['repositories'] as $repo) { - if (isset($repo['packagist']) && $repo['packagist'] === false) { - $loadPackagist = false; - } - } - } - if ($loadPackagist) { - $this->addPackagistRepository($rm); - } + $packageConfig = $this->addPackagistRepository($packageConfig); // load local repository $this->addLocalRepository($rm, $vendorDir); @@ -103,6 +93,9 @@ class Factory // initialize installation manager $im = $this->createInstallationManager($rm, $dm, $vendorDir, $binDir, $io); + // purge packages if they have been deleted on the filesystem + $this->purgePackages($rm, $im); + // init locker $lockFile = substr($composerFile, -5) === '.json' ? substr($composerFile, 0, -4).'lock' : $composerFile . '.lock'; $locker = new Package\Locker(new JsonFile($lockFile), $rm, md5_file($composerFile)); @@ -137,9 +130,33 @@ class Factory $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json'))); } - protected function addPackagistRepository(RepositoryManager $rm) + protected function addPackagistRepository(array $packageConfig) { - $rm->addRepository(new Repository\ComposerRepository(array('url' => 'http://packagist.org'))); + $loadPackagist = true; + $packagistConfig = array( + 'type' => 'composer', + 'url' => 'http://packagist.org' + ); + if (isset($packageConfig['repositories'])) { + foreach ($packageConfig['repositories'] as $key => $repo) { + if (isset($repo['packagist'])) { + if (true === $repo['packagist']) { + $packageConfig['repositories'][$key] = $packagistConfig; + } + + $loadPackagist = false; + break; + } + } + } else { + $packageConfig['repositories'] = array(); + } + + if ($loadPackagist) { + $packageConfig['repositories'][] = $packagistConfig; + } + + return $packageConfig; } public function createDownloadManager(IOInterface $io) @@ -166,6 +183,15 @@ class Factory return $im; } + protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im) + { + foreach ($rm->getLocalRepository()->getPackages() as $package) { + if (!$im->isPackageInstalled($package)) { + $rm->getLocalRepository()->removePackage($package); + } + } + } + static public function create(IOInterface $io, $composerFile = null) { $factory = new static(); diff --git a/src/Composer/Package/Loader/RootPackageLoader.php b/src/Composer/Package/Loader/RootPackageLoader.php index 77b2599fb..734e28c1a 100644 --- a/src/Composer/Package/Loader/RootPackageLoader.php +++ b/src/Composer/Package/Loader/RootPackageLoader.php @@ -68,7 +68,7 @@ class RootPackageLoader extends ArrayLoader throw new \UnexpectedValueException('Repository '.$index.' should be an array, '.gettype($repo).' given'); } if (!isset($repo['type'])) { - throw new \UnexpectedValueException('Repository '.$index.' must have a type defined'); + throw new \UnexpectedValueException('Repository '.$index.' ('.json_encode($repo).') must have a type defined'); } $repository = $this->manager->createRepository($repo['type'], $repo); $this->manager->addRepository($repository); diff --git a/src/Composer/Repository/Vcs/GitHubDriver.php b/src/Composer/Repository/Vcs/GitHubDriver.php index 01371c1a2..95701ddd6 100644 --- a/src/Composer/Repository/Vcs/GitHubDriver.php +++ b/src/Composer/Repository/Vcs/GitHubDriver.php @@ -2,8 +2,11 @@ namespace Composer\Repository\Vcs; +use Composer\Downloader\TransportException; use Composer\Json\JsonFile; use Composer\IO\IOInterface; +use Composer\Util\ProcessExecutor; +use Composer\Util\RemoteFilesystem; /** * @author Jordi Boggiano @@ -16,14 +19,30 @@ class GitHubDriver extends VcsDriver protected $branches; protected $rootIdentifier; protected $infoCache = array(); + protected $isPrivate = false; - public function __construct($url, IOInterface $io) + /** + * Git Driver + * + * @var GitDriver + */ + protected $gitDriver; + + /** + * Constructor + * + * @param string $url + * @param IOInterface $io + * @param ProcessExecutor $process + * @param RemoteFilesystem $remoteFilesystem + */ + public function __construct($url, IOInterface $io, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null) { preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url, $match); $this->owner = $match[1]; $this->repository = $match[2]; - parent::__construct($url, $io); + parent::__construct($url, $io, $process, $remoteFilesystem); } /** @@ -31,6 +50,7 @@ class GitHubDriver extends VcsDriver */ public function initialize() { + $this->fetchRootIdentifier(); } /** @@ -38,11 +58,9 @@ class GitHubDriver extends VcsDriver */ public function getRootIdentifier() { - if (null === $this->rootIdentifier) { - $repoData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository)); - $this->rootIdentifier = $repoData['master_branch'] ?: 'master'; + if ($this->gitDriver) { + return $this->gitDriver->getRootIdentifier(); } - return $this->rootIdentifier; } @@ -51,6 +69,9 @@ class GitHubDriver extends VcsDriver */ public function getUrl() { + if ($this->gitDriver) { + return $this->gitDriver->getUrl(); + } return $this->url; } @@ -59,9 +80,19 @@ class GitHubDriver extends VcsDriver */ public function getSource($identifier) { + if ($this->gitDriver) { + return $this->gitDriver->getSource($identifier); + } $label = array_search($identifier, $this->getTags()) ?: $identifier; + if ($this->isPrivate) { + // Private GitHub repositories should be accessed using the + // SSH version of the URL. + $url = $this->generateSshUrl(); + } else { + $url = $this->getUrl(); + } - return array('type' => 'git', 'url' => $this->getUrl(), 'reference' => $label); + return array('type' => 'git', 'url' => $url, 'reference' => $label); } /** @@ -69,6 +100,9 @@ class GitHubDriver extends VcsDriver */ public function getDist($identifier) { + if ($this->gitDriver) { + return $this->gitDriver->getDist($identifier); + } $label = array_search($identifier, $this->getTags()) ?: $identifier; $url = $this->getScheme() . '://github.com/'.$this->owner.'/'.$this->repository.'/zipball/'.$label; @@ -80,6 +114,9 @@ class GitHubDriver extends VcsDriver */ public function getComposerInformation($identifier) { + if ($this->gitDriver) { + return $this->gitDriver->getComposerInformation($identifier); + } if (!isset($this->infoCache[$identifier])) { $composer = $this->getContents($this->getScheme() . '://raw.github.com/'.$this->owner.'/'.$this->repository.'/'.$identifier.'/composer.json'); if (!$composer) { @@ -103,6 +140,9 @@ class GitHubDriver extends VcsDriver */ public function getTags() { + if ($this->gitDriver) { + return $this->gitDriver->getTags(); + } if (null === $this->tags) { $tagsData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/tags')); $this->tags = array(); @@ -119,6 +159,9 @@ class GitHubDriver extends VcsDriver */ public function getBranches() { + if ($this->gitDriver) { + return $this->gitDriver->getBranches(); + } if (null === $this->branches) { $branchData = JsonFile::parseJson($this->getContents($this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository.'/branches')); $this->branches = array(); @@ -137,4 +180,63 @@ class GitHubDriver extends VcsDriver { return extension_loaded('openssl') && preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url); } + + /** + * Generate an SSH URL + * + * @return string + */ + protected function generateSshUrl() + { + return 'git@github.com:'.$this->owner.'/'.$this->repository.'.git'; + } + + /** + * Fetch root identifier from GitHub + * + * @throws TransportException + */ + protected function fetchRootIdentifier() + { + $repoDataUrl = $this->getScheme() . '://api.github.com/repos/'.$this->owner.'/'.$this->repository; + $attemptCounter = 0; + while (null === $this->rootIdentifier) { + if (5 == $attemptCounter++) { + throw new \RuntimeException("Either you have entered invalid credentials or this GitHub repository does not exists (404)"); + } + try { + $repoData = JsonFile::parseJson($this->getContents($repoDataUrl)); + $this->rootIdentifier = $repoData['master_branch'] ?: 'master'; + } catch (TransportException $e) { + switch($e->getCode()) { + case 401: + case 404: + $this->isPrivate = true; + if (!$this->io->isInteractive()) { + // If this repository may be private (hard to say for sure, + // GitHub returns 404 for private repositories) and we + // cannot ask for authentication credentials (because we + // are not interactive) then we fallback to GitDriver. + $this->gitDriver = new GitDriver( + $this->generateSshUrl(), + $this->io, + $this->process, + $this->remoteFilesystem + ); + $this->gitDriver->initialize(); + return; + } + $this->io->write('Authentication required ('.$this->url.'):'); + $username = $this->io->ask('Username: '); + $password = $this->io->askAndHideAnswer('Password: '); + $this->io->setAuthorization($this->url, $username, $password); + break; + + default: + throw $e; + break; + } + } + } + } } diff --git a/src/Composer/Repository/Vcs/VcsDriver.php b/src/Composer/Repository/Vcs/VcsDriver.php index a20e959fd..99c705c50 100644 --- a/src/Composer/Repository/Vcs/VcsDriver.php +++ b/src/Composer/Repository/Vcs/VcsDriver.php @@ -27,6 +27,7 @@ abstract class VcsDriver implements VcsDriverInterface protected $url; protected $io; protected $process; + protected $remoteFilesystem; /** * Constructor. @@ -34,12 +35,14 @@ abstract class VcsDriver implements VcsDriverInterface * @param string $url The URL * @param IOInterface $io The IO instance * @param ProcessExecutor $process Process instance, injectable for mocking + * @param callable $remoteFilesystem Remote Filesystem, injectable for mocking */ - public function __construct($url, IOInterface $io, ProcessExecutor $process = null) + public function __construct($url, IOInterface $io, ProcessExecutor $process = null, $remoteFilesystem = null) { $this->url = $url; $this->io = $io; $this->process = $process ?: new ProcessExecutor; + $this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io); } /** @@ -80,8 +83,7 @@ abstract class VcsDriver implements VcsDriverInterface */ protected function getContents($url) { - $rfs = new RemoteFilesystem($this->io); - return $rfs->getContents($this->url, $url, false); + return $this->remoteFilesystem->getContents($this->url, $url, false); } protected static function isLocalUrl($url) diff --git a/src/Composer/Util/RemoteFilesystem.php b/src/Composer/Util/RemoteFilesystem.php index 87d725037..e14201a13 100644 --- a/src/Composer/Util/RemoteFilesystem.php +++ b/src/Composer/Util/RemoteFilesystem.php @@ -80,13 +80,11 @@ class RemoteFilesystem * @param string $fileUrl The file URL * @param string $fileName the local filename * @param boolean $progress Display the progression - * @param boolean $firstCall Whether this is the first attempt at fetching this resource * * @throws TransportException When the file could not be downloaded */ - protected function get($originUrl, $fileUrl, $fileName = null, $progress = true, $firstCall = true) + protected function get($originUrl, $fileUrl, $fileName = null, $progress = true) { - $this->firstCall = $firstCall; $this->bytesMax = 0; $this->result = null; $this->originUrl = $originUrl; @@ -102,10 +100,9 @@ class RemoteFilesystem $this->io->write(" Downloading: connection...", false); } + $result = @file_get_contents($fileUrl, false, $ctx); if (null !== $fileName) { - $result = @copy($fileUrl, $fileName, $ctx); - } else { - $result = @file_get_contents($fileUrl, false, $ctx); + $result = @file_put_contents($fileName, $result) ? true : false; } // fix for 5.4.0 https://bugs.php.net/bug.php?id=61336 @@ -140,20 +137,12 @@ class RemoteFilesystem protected function callbackGet($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) { switch ($notificationCode) { - case STREAM_NOTIFY_AUTH_REQUIRED: case STREAM_NOTIFY_FAILURE: - if (404 === $messageCode && !$this->firstCall) { - throw new TransportException("The '" . $this->fileUrl . "' URL not found", 404); - } - - // for private repository returning 404 error when the authorization is incorrect - $auth = $this->io->getAuthorization($this->originUrl); - $attemptAuthentication = $this->firstCall && 404 === $messageCode && null === $auth['username']; - - $this->firstCall = false; + throw new TransportException(trim($message), $messageCode); + break; - // get authorization informations - if (401 === $messageCode || $attemptAuthentication) { + case STREAM_NOTIFY_AUTH_REQUIRED: + if (401 === $messageCode) { if (!$this->io->isInteractive()) { $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console"; @@ -165,7 +154,7 @@ class RemoteFilesystem $password = $this->io->askAndHideAnswer(' Password: '); $this->io->setAuthorization($this->originUrl, $username, $password); - $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress, false); + $this->get($this->originUrl, $this->fileUrl, $this->fileName, $this->progress); } break; diff --git a/src/bootstrap.php b/src/bootstrap.php new file mode 100644 index 000000000..b9c5094af --- /dev/null +++ b/src/bootstrap.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +function includeIfExists($file) { + if (file_exists($file)) { + return include $file; + } +} + +if ((!$loader = includeIfExists(__DIR__.'/../../../.composer/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../vendor/.composer/autoload.php'))) { + die('You must set up the project dependencies, run the following commands:'.PHP_EOL. + 'curl -s http://getcomposer.org/installer | php'.PHP_EOL. + 'php composer.phar install'.PHP_EOL); +} + +return $loader; \ No newline at end of file diff --git a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php index 82f913d36..4bb7c9e33 100644 --- a/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php +++ b/tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php @@ -80,7 +80,7 @@ class DefaultPolicyTest extends TestCase $this->assertEquals($expected, $selected); } - public function testSelectLastRepo() + public function testSelectFirstRepo() { $this->repoImportant = new ArrayRepository; @@ -88,8 +88,8 @@ class DefaultPolicyTest extends TestCase $this->repoImportant->addPackage($packageAImportant = $this->getPackage('A', '1.0')); $this->pool->addRepository($this->repoInstalled); - $this->pool->addRepository($this->repo); $this->pool->addRepository($this->repoImportant); + $this->pool->addRepository($this->repo); $literals = array(new Literal($packageA, true), new Literal($packageAImportant, true)); $expected = array(new Literal($packageAImportant, true)); diff --git a/tests/Composer/Test/DependencyResolver/PoolTest.php b/tests/Composer/Test/DependencyResolver/PoolTest.php index 643be0428..c98570534 100644 --- a/tests/Composer/Test/DependencyResolver/PoolTest.php +++ b/tests/Composer/Test/DependencyResolver/PoolTest.php @@ -54,7 +54,44 @@ class PoolTest extends TestCase $secondPriority = $pool->getPriority($secondRepository); $this->assertEquals(0, $firstPriority); - $this->assertEquals(1, $secondPriority); + $this->assertEquals(-1, $secondPriority); + } + + public function testWhatProvidesSamePackageForDifferentRepositories() + { + $pool = new Pool; + $firstRepository = new ArrayRepository; + $secondRepository = new ArrayRepository; + + $firstPackage = $this->getPackage('foo', '1'); + $secondPackage = $this->getPackage('foo', '1'); + $thirdPackage = $this->getPackage('foo', '2'); + + $firstRepository->addPackage($firstPackage); + $secondRepository->addPackage($secondPackage); + $secondRepository->addPackage($thirdPackage); + + $pool->addRepository($firstRepository); + $pool->addRepository($secondRepository); + + $this->assertEquals(array($firstPackage, $secondPackage, $thirdPackage), $pool->whatProvides('foo')); + } + + public function testWhatProvidesPackageWithConstraint() + { + $pool = new Pool; + $repository = new ArrayRepository; + + $firstPackage = $this->getPackage('foo', '1'); + $secondPackage = $this->getPackage('foo', '2'); + + $repository->addPackage($firstPackage); + $repository->addPackage($secondPackage); + + $pool->addRepository($repository); + + $this->assertEquals(array($firstPackage, $secondPackage), $pool->whatProvides('foo')); + $this->assertEquals(array($secondPackage), $pool->whatProvides('foo', $this->getVersionConstraint('==', '2'))); } public function testPackageById() diff --git a/tests/Composer/Test/DependencyResolver/RequestTest.php b/tests/Composer/Test/DependencyResolver/RequestTest.php index e5010e0e4..d11c3c427 100644 --- a/tests/Composer/Test/DependencyResolver/RequestTest.php +++ b/tests/Composer/Test/DependencyResolver/RequestTest.php @@ -47,6 +47,32 @@ class RequestTest extends TestCase $request->getJobs()); } + public function testRequestInstallSamePackageFromDifferentRepositories() + { + $pool = new Pool; + $repo1 = new ArrayRepository; + $repo2 = new ArrayRepository; + + $foo1 = $this->getPackage('foo', '1'); + $foo2 = $this->getPackage('foo', '1'); + + $repo1->addPackage($foo1); + $repo2->addPackage($foo2); + + $pool->addRepository($repo1); + $pool->addRepository($repo2); + + $request = new Request($pool); + $request->install('foo', $this->getVersionConstraint('=', '1')); + + $this->assertEquals( + array( + array('packages' => array($foo1, $foo2), 'cmd' => 'install', 'packageName' => 'foo'), + ), + $request->getJobs() + ); + } + public function testUpdateAll() { $pool = new Pool; diff --git a/tests/Composer/Test/DependencyResolver/SolverTest.php b/tests/Composer/Test/DependencyResolver/SolverTest.php index fe6782177..fc7dce7a1 100644 --- a/tests/Composer/Test/DependencyResolver/SolverTest.php +++ b/tests/Composer/Test/DependencyResolver/SolverTest.php @@ -9,7 +9,6 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ - namespace Composer\Test\DependencyResolver; use Composer\Repository\ArrayRepository; @@ -70,6 +69,25 @@ class SolverTest extends TestCase } } + public function testSolverInstallSamePackageFromDifferentRepositories() + { + $repo1 = new ArrayRepository; + $repo2 = new ArrayRepository; + + $repo1->addPackage($foo1 = $this->getPackage('foo', '1')); + $repo2->addPackage($foo2 = $this->getPackage('foo', '1')); + + $this->pool->addRepository($this->repoInstalled); + $this->pool->addRepository($repo1); + $this->pool->addRepository($repo2); + + $this->request->install('foo'); + + $this->checkSolverResult(array( + array('job' => 'install', 'package' => $foo1), + )); + } + public function testSolverInstallWithDeps() { $this->repo->addPackage($packageA = $this->getPackage('A', '1.0')); diff --git a/tests/Composer/Test/Downloader/FileDownloaderTest.php b/tests/Composer/Test/Downloader/FileDownloaderTest.php index cc0fb2f6a..a93deaac5 100644 --- a/tests/Composer/Test/Downloader/FileDownloaderTest.php +++ b/tests/Composer/Test/Downloader/FileDownloaderTest.php @@ -17,6 +17,13 @@ use Composer\Util\Filesystem; class FileDownloaderTest extends \PHPUnit_Framework_TestCase { + protected function getDownloader($io = null, $rfs = null) + { + $io = $io ?: $this->getMock('Composer\IO\IOInterface'); + $rfs = $rfs ?: $this->getMockBuilder('Composer\Util\RemoteFilesystem')->disableOriginalConstructor()->getMock(); + return new FileDownloader($io, $rfs); + } + /** * @expectedException \InvalidArgumentException */ @@ -28,7 +35,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue(null)) ; - $downloader = new FileDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader = $this->getDownloader(); $downloader->download($packageMock, '/path'); } @@ -42,7 +49,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase $path = tempnam(sys_get_temp_dir(), 'c'); - $downloader = new FileDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader = $this->getDownloader(); try { $downloader->download($packageMock, $path); $this->fail(); @@ -63,7 +70,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue('http://example.com/script.js')) ; - $downloader = new FileDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader = $this->getDownloader(); $method = new \ReflectionMethod($downloader, 'getFileName'); $method->setAccessible(true); @@ -93,7 +100,7 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase })) ; - $downloader = new FileDownloader($ioMock); + $downloader = $this->getDownloader($ioMock); try { $downloader->download($packageMock, $path); $this->fail(); @@ -126,7 +133,12 @@ class FileDownloaderTest extends \PHPUnit_Framework_TestCase $path = sys_get_temp_dir().'/'.md5(time().rand()); } while (file_exists($path)); - $downloader = new FileDownloader($this->getMock('Composer\IO\IOInterface')); + $downloader = $this->getDownloader(); + + // make sure the file expected to be downloaded is on disk already + mkdir($path, 0777, true); + touch($path.'/script.js'); + try { $downloader->download($packageMock, $path); $this->fail(); diff --git a/tests/Composer/Test/Downloader/GitDownloaderTest.php b/tests/Composer/Test/Downloader/GitDownloaderTest.php index b6eed80a1..3decf991b 100644 --- a/tests/Composer/Test/Downloader/GitDownloaderTest.php +++ b/tests/Composer/Test/Downloader/GitDownloaderTest.php @@ -41,7 +41,6 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase public function testDownload() { - $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') @@ -50,6 +49,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->method('getSourceUrl') ->will($this->returnValue('https://example.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); + + $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'"); $processExecutor->expects($this->once()) ->method('execute') ->with($this->equalTo($expectedGitCommand)) @@ -59,13 +60,13 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase $downloader->download($packageMock, 'composerPath'); } - public function testDownloadUsesVariousProtocolsForGithub() + public function testDownloadUsesVariousProtocolsAndSetsPushUrlForGithub() { $packageMock = $this->getMock('Composer\Package\PackageInterface'); $packageMock->expects($this->any()) ->method('getSourceReference') ->will($this->returnValue('ref')); - $packageMock->expects($this->once()) + $packageMock->expects($this->any()) ->method('getSourceUrl') ->will($this->returnValue('https://github.com/composer/composer')); $processExecutor = $this->getMock('Composer\Util\ProcessExecutor'); @@ -88,6 +89,12 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase ->with($this->equalTo($expectedGitCommand)) ->will($this->returnValue(0)); + $expectedGitCommand = $this->getCmd("cd 'composerPath' && git remote set-url --push origin 'git@github.com:composer/composer.git'"); + $processExecutor->expects($this->at(3)) + ->method('execute') + ->with($this->equalTo($expectedGitCommand)) + ->will($this->returnValue(0)); + $downloader = $this->getDownloaderMock(null, $processExecutor); $downloader->download($packageMock, 'composerPath'); } diff --git a/tests/Composer/Test/FactoryTest.php b/tests/Composer/Test/FactoryTest.php new file mode 100644 index 000000000..2bb838fef --- /dev/null +++ b/tests/Composer/Test/FactoryTest.php @@ -0,0 +1,65 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Factory; + +class FactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider dataAddPackagistRepository + */ + public function testAddPackagistRepository($expected, $config) + { + $factory = new Factory(); + + $ref = new \ReflectionMethod($factory, 'addPackagistRepository'); + $ref->setAccessible(true); + + $this->assertEquals($expected, $ref->invoke($factory, $config)); + } + + public function dataAddPackagistRepository() + { + $f = function() { + $repositories = func_get_args(); + return array('repositories' => $repositories); + }; + + $data = array(); + $data[] = array( + $f(array('type' => 'composer', 'url' => 'http://packagist.org')), + $f() + ); + + $data[] = array( + $f(array('packagist' => false)), + $f(array('packagist' => false)) + ); + + $data[] = array( + $f( + array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), + array('type' => 'composer', 'url' => 'http://packagist.org'), + array('type' => 'pear', 'url' => 'http://pear.composer.org') + ), + $f( + array('type' => 'vcs', 'url' => 'git://github.com/composer/composer.git'), + array('packagist' => true), + array('type' => 'pear', 'url' => 'http://pear.composer.org') + ) + ); + + return $data; + } +} diff --git a/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php new file mode 100644 index 000000000..2d0217006 --- /dev/null +++ b/tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php @@ -0,0 +1,234 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Repository\Vcs; + +use Composer\Downloader\TransportException; +use Composer\Repository\Vcs\GitHubDriver; +use Composer\Util\Filesystem; + +/** + * @author Beau Simensen + */ +class GitHubDriverTest extends \PHPUnit_Framework_TestCase +{ + public function testPrivateRepository() + { + $scheme = extension_loaded('openssl') ? 'https' : 'http'; + + $repoUrl = 'http://github.com/composer/packagist'; + $repoApiUrl = $scheme.'://api.github.com/repos/composer/packagist'; + $repoSshUrl = 'git@github.com:composer/packagist.git'; + $identifier = 'v0.0.0'; + $sha = 'SOMESHA'; + + $io = $this->getMock('Composer\IO\IOInterface'); + $io->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue(true)); + + $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + ->setConstructorArgs(array($io)) + ->getMock(); + + $remoteFilesystem->expects($this->at(0)) + ->method('getContents') + ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) + ->will($this->throwException(new TransportException('HTTP/1.1 404 Not Found', 404))); + + $io->expects($this->once()) + ->method('ask') + ->with($this->equalTo('Username: ')) + ->will($this->returnValue('someuser')); + + $io->expects($this->once()) + ->method('askAndHideAnswer') + ->with($this->equalTo('Password: ')) + ->will($this->returnValue('somepassword')); + + $io->expects($this->once()) + ->method('setAuthorization') + ->with($this->equalTo($repoUrl), 'someuser', 'somepassword'); + + $remoteFilesystem->expects($this->at(1)) + ->method('getContents') + ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) + ->will($this->returnValue('{"master_branch": "test_master"}')); + + $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem); + $gitHubDriver->initialize(); + $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); + + $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + + $dist = $gitHubDriver->getDist($identifier); + $this->assertEquals('zip', $dist['type']); + $this->assertEquals($scheme.'://github.com/composer/packagist/zipball/v0.0.0', $dist['url']); + $this->assertEquals('v0.0.0', $dist['reference']); + + $source = $gitHubDriver->getSource($identifier); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoSshUrl, $source['url']); + $this->assertEquals('v0.0.0', $source['reference']); + + $dist = $gitHubDriver->getDist($sha); + $this->assertEquals('zip', $dist['type']); + $this->assertEquals($scheme.'://github.com/composer/packagist/zipball/v0.0.0', $dist['url']); + $this->assertEquals('v0.0.0', $dist['reference']); + + $source = $gitHubDriver->getSource($sha); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoSshUrl, $source['url']); + $this->assertEquals('v0.0.0', $source['reference']); + } + + public function testPublicRepository() + { + $scheme = extension_loaded('openssl') ? 'https' : 'http'; + + $repoUrl = 'http://github.com/composer/packagist'; + $repoApiUrl = $scheme.'://api.github.com/repos/composer/packagist'; + $identifier = 'v0.0.0'; + $sha = 'SOMESHA'; + + $io = $this->getMock('Composer\IO\IOInterface'); + $io->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue(true)); + + $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + ->setConstructorArgs(array($io)) + ->getMock(); + + $remoteFilesystem->expects($this->at(0)) + ->method('getContents') + ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) + ->will($this->returnValue('{"master_branch": "test_master"}')); + + $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem); + $gitHubDriver->initialize(); + $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha)); + + $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + + $dist = $gitHubDriver->getDist($identifier); + $this->assertEquals('zip', $dist['type']); + $this->assertEquals($scheme.'://github.com/composer/packagist/zipball/v0.0.0', $dist['url']); + $this->assertEquals($identifier, $dist['reference']); + + $source = $gitHubDriver->getSource($identifier); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoUrl, $source['url']); + $this->assertEquals($identifier, $source['reference']); + + $dist = $gitHubDriver->getDist($sha); + $this->assertEquals('zip', $dist['type']); + $this->assertEquals($scheme.'://github.com/composer/packagist/zipball/v0.0.0', $dist['url']); + $this->assertEquals($identifier, $dist['reference']); + + $source = $gitHubDriver->getSource($sha); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoUrl, $source['url']); + $this->assertEquals($identifier, $source['reference']); + } + + public function testPrivateRepositoryNoInteraction() + { + $scheme = extension_loaded('openssl') ? 'https' : 'http'; + + $repoUrl = 'http://github.com/composer/packagist'; + $repoApiUrl = $scheme.'://api.github.com/repos/composer/packagist'; + $repoSshUrl = 'git@github.com:composer/packagist.git'; + $identifier = 'v0.0.0'; + $sha = 'SOMESHA'; + + $process = $this->getMockBuilder('Composer\Util\ProcessExecutor') + ->disableOriginalConstructor() + ->getMock(); + + $io = $this->getMock('Composer\IO\IOInterface'); + $io->expects($this->any()) + ->method('isInteractive') + ->will($this->returnValue(false)); + + $remoteFilesystem = $this->getMockBuilder('Composer\Util\RemoteFilesystem') + ->setConstructorArgs(array($io)) + ->getMock(); + + $remoteFilesystem->expects($this->at(0)) + ->method('getContents') + ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false)) + ->will($this->throwException(new TransportException('HTTP/1.1 404 Not Found', 404))); + + // clean local clone if present + $fs = new Filesystem(); + $fs->removeDirectory(sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $repoSshUrl) . '/'); + + $process->expects($this->at(0)) + ->method('execute') + ->with($this->stringContains($repoSshUrl)); + + $process->expects($this->at(1)) + ->method('execute') + ->with($this->stringContains('git tag')); + + $process->expects($this->at(2)) + ->method('splitLines') + ->will($this->returnValue(array($identifier))); + + $process->expects($this->at(3)) + ->method('execute') + ->with($this->stringContains('git branch')); + + $process->expects($this->at(4)) + ->method('splitLines') + ->will($this->returnValue(array(' test_master edf93f1fccaebd8764383dc12016d0a1a9672d89 Fix test & behavior'))); + + $process->expects($this->at(5)) + ->method('execute') + ->with($this->stringContains('git branch')); + + $process->expects($this->at(6)) + ->method('splitLines') + ->will($this->returnValue(array(' upstream/HEAD -> upstream/test_master'))); + + $gitHubDriver = new GitHubDriver($repoUrl, $io, $process, $remoteFilesystem); + $gitHubDriver->initialize(); + + $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier()); + + // Dist is not available for GitDriver + $dist = $gitHubDriver->getDist($identifier); + $this->assertNull($dist); + + $source = $gitHubDriver->getSource($identifier); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoSshUrl, $source['url']); + $this->assertEquals($identifier, $source['reference']); + + // Dist is not available for GitDriver + $dist = $gitHubDriver->getDist($sha); + $this->assertNull($dist); + + $source = $gitHubDriver->getSource($sha); + $this->assertEquals('git', $source['type']); + $this->assertEquals($repoSshUrl, $source['url']); + $this->assertEquals($sha, $source['reference']); + } + + protected function setAttribute($object, $attribute, $value) + { + $attr = new \ReflectionProperty($object, $attribute); + $attr->setAccessible(true); + $attr->setValue($object, $value); + } +} diff --git a/tests/Composer/Test/Util/RemoteFilesystemTest.php b/tests/Composer/Test/Util/RemoteFilesystemTest.php index bee389941..97e50b848 100644 --- a/tests/Composer/Test/Util/RemoteFilesystemTest.php +++ b/tests/Composer/Test/Util/RemoteFilesystemTest.php @@ -105,41 +105,14 @@ class RemoteFilesystemTest extends \PHPUnit_Framework_TestCase public function testCallbackGetNotifyFailure404() { $fs = new RemoteFilesystem($this->getMock('Composer\IO\IOInterface')); - $this->setAttribute($fs, 'firstCall', false); try { - $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0); + $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, 'HTTP/1.1 404 Not Found', 404, 0, 0); $this->fail(); } catch (\Exception $e) { $this->assertInstanceOf('Composer\Downloader\TransportException', $e); - $this->assertContains('URL not found', $e->getMessage()); - } - } - - public function testCallbackGetNotifyFailure404FirstCall() - { - $io = $this->getMock('Composer\IO\IOInterface'); - $io - ->expects($this->once()) - ->method('getAuthorization') - ->will($this->returnValue(array('username' => null))) - ; - $io - ->expects($this->once()) - ->method('isInteractive') - ->will($this->returnValue(false)) - ; - - $fs = new RemoteFilesystem($io); - $this->setAttribute($fs, 'firstCall', true); - - try { - $this->callCallbackGet($fs, STREAM_NOTIFY_FAILURE, 0, '', 404, 0, 0); - $this->fail(); - } catch (\Exception $e) { - $this->assertInstanceOf('Composer\Downloader\TransportException', $e); - $this->assertContains('URL required authentication', $e->getMessage()); - $this->assertAttributeEquals(false, 'firstCall', $fs); + $this->assertEquals(404, $e->getCode()); + $this->assertContains('HTTP/1.1 404 Not Found', $e->getMessage()); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 02bd0e169..a4f2e7cec 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -10,10 +10,5 @@ * file that was distributed with this source code. */ -if ((!$loader = @include __DIR__.'/../../../.composer/autoload.php') && (!$loader = @include __DIR__.'/../vendor/.composer/autoload.php')) { - die('You must set up the project dependencies, run the following commands:'.PHP_EOL. - 'curl -s http://getcomposer.org/installer | php'.PHP_EOL. - 'php composer.phar install'.PHP_EOL); -} - +$loader = require __DIR__.'/../src/bootstrap.php'; $loader->add('Composer\Test', __DIR__);