1
0
Fork 0
Conflicts:
	src/Composer/Factory.php
pull/1407/head
Nicolas Toniazzi 2014-12-15 14:36:43 +01:00
commit ad9c3d3b30
65 changed files with 1060 additions and 236 deletions

View File

@ -1,3 +1,33 @@
### 1.0.0-alpha9 (2014-12-07)
* Added `remove` command to do the reverse of `require`
* Added --ignore-platform-reqs to `install`/`update` commands to install even if you are missing a php extension or have an invalid php version
* Added a warning when abandoned packages are being installed
* Added auto-selection of the version constraint in the `require` command, which can now be used simply as `composer require foo/bar`
* Added ability to define custom composer commands using scripts
* Added `browse` command to open a browser to the given package's repo URL (or homepage with `-H`)
* Added an `autoload-dev` section to declare dev-only autoload rules + a --no-dev flag to dump-autoload
* Added an `auth.json` file, with `store-auths` config option
* Added a `http-basic` config option to store login/pwds to hosts
* Added failover to source/dist and vice-versa in case a download method fails
* Added --path (-P) flag to the show command to see the install path of packages
* Added --update-with-dependencies and --update-no-dev flags to the require command
* Added `optimize-autoloader` config option to force the `-o` flag from the config
* Added `clear-cache` command
* Added a GzipDownloader to download single gzipped files
* Added `ssh` support in the `github-protocols` config option
* Added `pre-dependencies-solving` and `post-dependencies-solving` events
* Added `pre-archive-cmd` and `post-archive-cmd` script events to the `archive` command
* Added a `no-api` flag to GitHub VCS repos to skip the API but still get zip downloads
* Added http-basic auth support for private git repos not on github
* Added support for autoloading `.hh` files when running HHVM
* Added support for PHP 5.6
* Added support for OTP auth when retrieving a GitHub API key
* Fixed isolation of `files` autoloaded scripts to ensure they can not affect anything
* Improved performance of solving dependencies
* Improved SVN and Perforce support
* A boatload of minor fixes, documentation additions and UX improvements
### 1.0.0-alpha8 (2014-01-06)
* Break: The `install` command now has --dev enabled by default. --no-dev can be used to install without dev requirements

134
composer.lock generated
View File

@ -120,17 +120,17 @@
},
{
"name": "symfony/console",
"version": "v2.6.0",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "d3bac228fd7a2aac9193e241b239880b3ba39a10"
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/d3bac228fd7a2aac9193e241b239880b3ba39a10",
"reference": "d3bac228fd7a2aac9193e241b239880b3ba39a10",
"url": "https://api.github.com/repos/symfony/Console/zipball/ef825fd9f809d275926547c9e57cbf14968793e8",
"reference": "ef825fd9f809d275926547c9e57cbf14968793e8",
"shasum": ""
},
"require": {
@ -173,21 +173,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2014-11-20 13:24:23"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/finder",
"version": "v2.6.0",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
"reference": "d574347c652a14cfee0349f744c7880e1d9029fd"
"reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Finder/zipball/d574347c652a14cfee0349f744c7880e1d9029fd",
"reference": "d574347c652a14cfee0349f744c7880e1d9029fd",
"url": "https://api.github.com/repos/symfony/Finder/zipball/0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721",
"reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721",
"shasum": ""
},
"require": {
@ -220,21 +220,21 @@
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
"time": "2014-11-28 10:00:40"
"time": "2014-12-02 20:19:20"
},
{
"name": "symfony/process",
"version": "v2.6.0",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Process",
"source": {
"type": "git",
"url": "https://github.com/symfony/Process.git",
"reference": "dc88f75d1c07791e5733f90be747961dce26cf05"
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Process/zipball/dc88f75d1c07791e5733f90be747961dce26cf05",
"reference": "dc88f75d1c07791e5733f90be747961dce26cf05",
"url": "https://api.github.com/repos/symfony/Process/zipball/bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a",
"shasum": ""
},
"require": {
@ -267,7 +267,7 @@
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com",
"time": "2014-11-04 14:29:39"
"time": "2014-12-02 20:19:20"
}
],
"packages-dev": [
@ -327,16 +327,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "2.0.12",
"version": "2.0.13",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "7ce9da20f96964bb7a4033f53834df13328dbeab"
"reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7ce9da20f96964bb7a4033f53834df13328dbeab",
"reference": "7ce9da20f96964bb7a4033f53834df13328dbeab",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
"reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
"shasum": ""
},
"require": {
@ -388,7 +388,7 @@
"testing",
"xunit"
],
"time": "2014-12-02 13:17:01"
"time": "2014-12-03 06:41:44"
},
{
"name": "phpunit/php-file-iterator",
@ -574,16 +574,16 @@
},
{
"name": "phpunit/phpunit",
"version": "4.3.5",
"version": "4.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1"
"reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
"reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0",
"reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0",
"shasum": ""
},
"require": {
@ -600,8 +600,9 @@
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.0",
"sebastian/diff": "~1.1",
"sebastian/environment": "~1.0",
"sebastian/environment": "~1.1",
"sebastian/exporter": "~1.0",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
"symfony/yaml": "~2.0"
},
@ -614,7 +615,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.3.x-dev"
"dev-master": "4.4.x-dev"
}
},
"autoload": {
@ -623,10 +624,6 @@
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"",
"../../symfony/yaml/"
],
"license": [
"BSD-3-Clause"
],
@ -638,13 +635,13 @@
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "http://www.phpunit.de/",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2014-11-11 10:11:09"
"time": "2014-12-05 06:49:03"
},
{
"name": "phpunit/phpunit-mock-objects",
@ -703,16 +700,16 @@
},
{
"name": "sebastian/comparator",
"version": "1.0.1",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef"
"reference": "c484a80f97573ab934e37826dba0135a3301b26a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
"reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/c484a80f97573ab934e37826dba0135a3301b26a",
"reference": "c484a80f97573ab934e37826dba0135a3301b26a",
"shasum": ""
},
"require": {
@ -726,7 +723,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@ -763,7 +760,7 @@
"compare",
"equality"
],
"time": "2014-05-11 23:00:21"
"time": "2014-11-16 21:32:38"
},
{
"name": "sebastian/diff",
@ -932,6 +929,57 @@
],
"time": "2014-09-10 00:51:36"
},
{
"name": "sebastian/global-state",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2014-10-06 09:23:50"
},
{
"name": "sebastian/version",
"version": "1.0.3",
@ -969,17 +1017,17 @@
},
{
"name": "symfony/yaml",
"version": "v2.6.0",
"version": "v2.6.1",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "51c845cf3e4bfc182d1d5c05ed1c7338361d86f8"
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/51c845cf3e4bfc182d1d5c05ed1c7338361d86f8",
"reference": "51c845cf3e4bfc182d1d5c05ed1c7338361d86f8",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/3346fc090a3eb6b53d408db2903b241af51dcb20",
"reference": "3346fc090a3eb6b53d408db2903b241af51dcb20",
"shasum": ""
},
"require": {
@ -1012,7 +1060,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2014-11-20 13:24:23"
"time": "2014-12-02 20:19:20"
}
],
"aliases": [],

View File

@ -47,7 +47,7 @@ any version beginning with `1.2`.
## System Requirements
Composer requires PHP 5.3.2+ to run. A few sensitive php settings and compile
flags are also required, but the installer will warn you about any
flags are also required, but when using the installer you will be warned about any
incompatibilities.
To install packages from sources instead of simple zip archives, you will need
@ -56,14 +56,17 @@ git, svn or hg depending on how the package is version-controlled.
Composer is multi-platform and we strive to make it run equally well on Windows,
Linux and OSX.
## Installation - *nix
## Installation - Linux / Unix / OSX
### Downloading the Composer Executable
There are in short, two ways to install Composer. Locally as part of your
project, or globally as a system wide executable.
#### Locally
To actually get Composer, we need to do two things. The first one is installing
Composer (again, this means downloading it into your project):
Installing Composer locally is a matter of just running the installer in your
project directory:
```sh
curl -sS https://getcomposer.org/installer | php
@ -76,8 +79,8 @@ curl -sS https://getcomposer.org/installer | php
php -r "readfile('https://getcomposer.org/installer');" | php
```
This will just check a few PHP settings and then download `composer.phar` to
your working directory. This file is the Composer binary. It is a PHAR (PHP
The installer will just check a few PHP settings and then download `composer.phar`
to your working directory. This file is the Composer binary. It is a PHAR (PHP
archive), which is an archive format for PHP which can be run on the command
line, amongst other things.
@ -106,17 +109,6 @@ mv composer.phar /usr/local/bin/composer
Then, just run `composer` in order to run Composer instead of `php composer.phar`.
#### Globally (on OSX via homebrew)
Composer is part of the homebrew-php project.
```sh
brew update
brew tap homebrew/dupes
brew tap homebrew/php
brew install composer
```
## Installation - Windows
### Using the Installer
@ -127,6 +119,9 @@ Download and run [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe
it will install the latest Composer version and set up your PATH so that you can
just call `composer` from any directory in your command line.
> **Note:** Close your current terminal. Test usage with a new terminal:
> That is important since the PATH only gets loaded when the terminal starts.
### Manual Installation
Change to a directory on your `PATH` and run the install snippet to download

View File

@ -1,29 +1,8 @@
# Basic usage
## Installation
## Installing
To install Composer, you just need to download the `composer.phar` executable.
```sh
curl -sS https://getcomposer.org/installer | php
```
For the details, see the [Introduction](00-intro.md) chapter.
To check if Composer is working, just run the PHAR through `php`:
```sh
php composer.phar
```
This should give you a list of available commands.
> **Note:** You can also perform the checks only without downloading Composer
> by using the `--check` option. For more information, just use `--help`.
>
> ```sh
> curl -sS https://getcomposer.org/installer | php -- --help
> ```
If you have not yet installed Composer, refer to to the [Intro](00-intro.md) chapter.
## `composer.json`: Project Setup
@ -72,17 +51,19 @@ means any version in the `1.0` development branch. It would match `1.0.0`,
Version constraints can be specified in a few different ways.
Name | Example | Description
-------------- | ------------------------------------------------------------------ | -----------
Exact version | `1.0.2` | You can specify the exact version of a package.
Range | `>=1.0` `>=1.0,<2.0` <code>&gt;=1.0,&lt;1.1 &#124; &gt;=1.2</code> | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. <br />You can define multiple ranges. Ranges separated by a comma (`,`) will be treated as a **logical AND**. A pipe (<code>&#124;</code>) will be treated as a **logical OR**. AND has higher precedence than OR.
Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1`.
Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2,<2.0`. For more details, read the next section below.
Name | Example | Description
-------------- | ------------------------------------------------------------------------ | -----------
Exact version | `1.0.2` | You can specify the exact version of a package.
Range | `>=1.0` `>=1.0 <2.0` <code>&gt;=1.0 &lt;1.1 &#124;&#124; &gt;=1.2</code> | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`. <br />You can define multiple ranges. Ranges separated by a space (<code> </code>) or comma (`,`) will be treated as a **logical AND**. A double pipe (<code>&#124;&#124;</code>) will be treated as a **logical OR**. AND has higher precedence than OR.
Hyphen Range | `1.0 - 2.0` | Inclusive set of versions. Partial versions on the right include are completed with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the `2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to `>=1.0.0 <=2.1.0`.
Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0 <1.1`.
Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2 <2.0`. For more details, read the next section below.
Caret Operator | `^1.2.3` | Very useful for projects that follow semantic versioning. `^1.2.3` is equivalent to `>=1.2.3 <2.0`. For more details, read the next section below.
### Next Significant Release (Tilde Operator)
### Next Significant Release (Tilde and Caret Operators)
The `~` operator is best explained by example: `~1.2` is equivalent to
`>=1.2,<2.0`, while `~1.2.3` is equivalent to `>=1.2.3,<1.3`. As you can see
`>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see
it is mostly useful for projects respecting [semantic
versioning](http://semver.org/). A common usage would be to mark the minimum
minor version you depend on, like `~1.2` (which allows anything up to, but not
@ -90,6 +71,12 @@ including, 2.0). Since in theory there should be no backwards compatibility
breaks until 2.0, that works well. Another way of looking at it is that using
`~` specifies a minimum version, but allows the last digit specified to go up.
The `^` operator behaves very similarly but it sticks closer to semantic
versioning, and will always allow non-breaking updates. For example `^1.2.3`
is equivalent to `>=1.2.3 <2.0.0` as none of the releases until 2.0 should
break backwards compatibility. For pre-1.0 versions it also acts with safety
in mind and treats `^0.3` as `>=0.3.0 <0.4.0`
> **Note:** Though `2.0-beta.1` is strictly before `2.0`, a version constraint
> like `~1.2` would not install it. As said above `~1.2` only means the `.2`
> can change but the `1.` part is fixed.

View File

@ -140,7 +140,9 @@ php composer.phar update vendor/*
* **--lock:** Only updates the lock file hash to suppress warning about the
lock file being out of date.
* **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
So all packages with their dependencies are updated recursively.
* **--prefer-stable:** Prefer stable versions of dependencies.
* **--prefer-lowest:** Prefer lowest versions of dependencies. Useful for testing minimal
versions of requirements, generally used with `--prefer-stable`.
## require
@ -482,11 +484,20 @@ performance.
a bit of time to run so it is currently not done by default.
* **--no-dev:** Disables autoload-dev rules.
## clear-cache
Deletes all content from Composer's cache directories.
## licenses
Lists the name, version and license of every package installed. Use
`--format=json` to get machine readable output.
### Options
* **--no-dev:** Remove dev dependencies from the output
* **--format:** Format of the output: text or json (default: "text")
## run-script
To run [scripts](articles/scripts.md) manually you can use this command,

View File

@ -345,10 +345,10 @@ dependencies from being installed.
Lists packages that conflict with this version of this package. They
will not be allowed to be installed together with your package.
Note that when specifying ranges like `<1.0, >= 1.1` in a `conflict` link,
Note that when specifying ranges like `<1.0 >=1.1` in a `conflict` link,
this will state a conflict with all versions that are less than 1.0 *and* equal
or newer than 1.1 at the same time, which is probably not what you want. You
probably want to go for `<1.0 | >= 1.1` in this case.
probably want to go for `<1.0 | >=1.1` in this case.
#### replace

View File

@ -0,0 +1,59 @@
<!--
tagline: Access privately hosted packages
-->
# HTTP basic authentication
Your [Satis or Toran Proxy](handling-private-packages-with-satis.md) server
could be secured with http basic authentication. In order to allow your project
to have access to these packages you will have to tell composer how to
authenticate with your credentials.
The simplest way to provide your credentials is providing your set
of credentials inline with the repository specification such as:
```json
{
"repositories": [
{
"type": "composer",
"url": "http://extremely:secret@repo.example.org"
}
]
}
```
This will basically teach composer how to authenticate automatically
when reading packages from the provided composer repository.
This does not work for everybody especially when you don't want to
hard code your credentials into your composer.json. There is a second
way to provide these details and it is via interaction. If you don't
provide the authentication credentials composer will prompt you upon
connection to enter the username and password.
The third way if you want to pre-configure it is via an `auth.json` file
located in your `COMPOSER_HOME` or besides your `composer.json`.
The file should contain a set of hostnames followed each with their own
username/password pairs, for example:
```json
{
"basic-auth": [
"repo.example1.org": {
"username": "my-username1",
"password": "my-secret-password1"
},
"repo.example2.org": {
"username": "my-username2",
"password": "my-secret-password2"
}
]
}
```
The main advantage of the auth.json file is that it can be gitignored so
that every developer in your team can place their own credentials in there,
which makes revokation of credentials much easier than if you all share the
same.

View File

@ -59,6 +59,7 @@ class ClassLoader
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}

View File

@ -68,6 +68,15 @@ abstract class Command extends BaseCommand
$this->composer = $composer;
}
/**
* Removes the cached composer instance
*/
public function resetComposer()
{
$this->composer = null;
$this->getApplication()->resetComposer();
}
/**
* @return IOInterface
*/

View File

@ -234,8 +234,14 @@ EOT
{
if (null === $repositoryUrl) {
$sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) {
$sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io, $config)));
} elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION) && file_exists($repositoryUrl)) {
$json = new JsonFile($repositoryUrl, new RemoteFilesystem($io, $config));
$data = $json->read();
if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
$sourceRepo = new ComposerRepository(array('url' => 'file://' . strtr(realpath($repositoryUrl), '\\', '/')), $io, $config);
} else {
$sourceRepo = new FilesystemRepository($json);
}
} elseif (0 === strpos($repositoryUrl, 'http')) {
$sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
} else {

View File

@ -255,7 +255,7 @@ EOT
$latest = trim($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false));
if (Composer::VERSION !== $latest && Composer::VERSION !== '@package_version@') {
return '<warning>Your are not running the latest version</warning>';
return '<warning>You are not running the latest version</warning>';
}
return true;

View File

@ -33,8 +33,9 @@ use Symfony\Component\Process\ExecutableFinder;
*/
class InitCommand extends Command
{
protected $repos;
private $gitConfig;
private $repos;
private $pool;
public function parseAuthorString($author)

View File

@ -16,6 +16,8 @@ use Composer\Json\JsonFile;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -33,6 +35,7 @@ class LicensesCommand extends Command
->setDescription('Show information about licenses of dependencies')
->setDefinition(array(
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
))
->setHelp(<<<EOT
The license command displays detailed information about the licenses of
@ -55,9 +58,10 @@ EOT
$versionParser = new VersionParser;
$packages = array();
foreach ($repo->getPackages() as $package) {
$packages[$package->getName()] = $package;
if ($input->getOption('no-dev')) {
$packages = $this->filterRequiredPackages($repo, $root);
} else {
$packages = $this->appendPackages($repo->getPackages(), array());
}
ksort($packages);
@ -102,4 +106,47 @@ EOT
throw new \RuntimeException(sprintf('Unsupported format "%s". See help for supported formats.', $format));
}
}
/**
* Find package requires and child requires
*
* @param RepositoryInterface $repo
* @param PackageInterface $package
*/
private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $package, $bucket = array())
{
$requires = array_keys($package->getRequires());
$packageListNames = array_keys($bucket);
$packages = array_filter(
$repo->getPackages(),
function ($package) use ($requires, $packageListNames) {
return in_array($package->getName(), $requires) && !in_array($package->getName(), $packageListNames);
}
);
$bucket = $this->appendPackages($packages, $bucket);
foreach ($packages as $package) {
$bucket = $this->filterRequiredPackages($repo, $package, $bucket);
}
return $bucket;
}
/**
* Adds packages to the package list
*
* @param array $packages the list of packages to add
* @param array $bucket the list to add packages to
* @return array
*/
public function appendPackages(array $packages, array $bucket)
{
foreach ($packages as $package) {
$bucket[$package->getName()] = $package;
}
return $bucket;
}
}

View File

@ -23,6 +23,8 @@ use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
/**
* @author Jérémy Romey <jeremy@free-agent.fr>
@ -45,6 +47,7 @@ class RequireCommand extends InitCommand
new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies.'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
))
->setHelp(<<<EOT
The require command adds required packages to your composer.json and installs them
@ -78,14 +81,22 @@ EOT
}
$json = new JsonFile($file);
$composer = $json->read();
$composerDefinition = $json->read();
$composerBackup = file_get_contents($json->getPath());
$composer = $this->getComposer();
$repos = $composer->getRepositoryManager()->getRepositories();
$this->repos = new CompositeRepository(array_merge(
array(new PlatformRepository),
$repos
));
$requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
$requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
$removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
$baseRequirements = array_key_exists($requireKey, $composer) ? $composer[$requireKey] : array();
$baseRequirements = array_key_exists($requireKey, $composerDefinition) ? $composerDefinition[$requireKey] : array();
$requirements = $this->formatRequirements($requirements);
// validate requirements format
@ -94,17 +105,19 @@ EOT
$versionParser->parseConstraints($constraint);
}
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey, $removeKey)) {
$sortPackages = $input->getOption('sort-packages');
if (!$this->updateFileCleanly($json, $baseRequirements, $requirements, $requireKey, $removeKey, $sortPackages)) {
foreach ($requirements as $package => $version) {
$baseRequirements[$package] = $version;
if (isset($composer[$removeKey][$package])) {
unset($composer[$removeKey][$package]);
if (isset($composerDefinition[$removeKey][$package])) {
unset($composerDefinition[$removeKey][$package]);
}
}
$composer[$requireKey] = $baseRequirements;
$json->write($composer);
$composerDefinition[$requireKey] = $baseRequirements;
$json->write($composerDefinition);
}
$output->writeln('<info>'.$file.' has been '.($newlyCreated ? 'created' : 'updated').'</info>');
@ -115,6 +128,7 @@ EOT
$updateDevMode = !$input->getOption('update-no-dev');
// Update packages
$this->resetComposer();
$composer = $this->getComposer();
$composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
$io = $this->getIO();
@ -149,14 +163,14 @@ EOT
return $status;
}
private function updateFileCleanly($json, array $base, array $new, $requireKey, $removeKey)
private function updateFileCleanly($json, array $base, array $new, $requireKey, $removeKey, $sortPackages)
{
$contents = file_get_contents($json->getPath());
$manipulator = new JsonManipulator($contents);
foreach ($new as $package => $constraint) {
if (!$manipulator->addLink($requireKey, $package, $constraint)) {
if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
return false;
}
if (!$manipulator->removeSubNode($removeKey, $package)) {

View File

@ -43,6 +43,7 @@ class SelfUpdateCommand extends Command
new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
))
->setHelp(<<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
@ -105,7 +106,7 @@ EOT
$output->writeln(sprintf("Updating to version <info>%s</info>.", $updateVersion));
$remoteFilename = $baseUrl . (preg_match('{^[0-9a-f]{40}$}', $updateVersion) ? '/composer.phar' : "/download/{$updateVersion}/composer.phar");
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename);
$remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
if (!file_exists($tempFilename)) {
$output->writeln('<error>The download of the new composer version failed for an unexpected reason</error>');

View File

@ -47,6 +47,8 @@ class UpdateCommand extends Command
new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
))
->setHelp(<<<EOT
The <info>update</info> command reads the composer.json file from the
@ -121,6 +123,8 @@ EOT
->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
->setWhitelistDependencies($input->getOption('with-dependencies'))
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
;
if ($input->getOption('no-plugins')) {

View File

@ -55,7 +55,7 @@ class Compiler
$date->setTimezone(new \DateTimeZone('UTC'));
$this->versionDate = $date->format('Y-m-d H:i:s');
$process = new Process('git describe --tags HEAD');
$process = new Process('git describe --tags --exact-match HEAD');
if ($process->run() == 0) {
$this->version = trim($process->getOutput());
} else {
@ -212,6 +212,16 @@ class Compiler
* the license that is located at the bottom of this file.
*/
// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && ini_get('apc.enable_cli') && ini_get('apc.cache_by_default')) {
if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
ini_set('apc.cache_by_default', 0);
} else {
fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
}
}
Phar::mapPhar('composer.phar');
EOF;

View File

@ -302,7 +302,7 @@ class Config
* This should be used to read COMPOSER_ environment variables
* that overload config values.
*
* @param string $var
* @param string $var
* @return string|boolean
*/
private function getComposerEnv($var)

View File

@ -99,7 +99,8 @@ class Application extends BaseApplication
if ($name = $this->getCommandName($input)) {
try {
$commandName = $this->find($name)->getName();
} catch (\InvalidArgumentException $e) {}
} catch (\InvalidArgumentException $e) {
}
}
if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
if (time() > COMPOSER_DEV_WARNING_TIME) {
@ -176,7 +177,7 @@ class Application extends BaseApplication
public function renderException($exception, $output)
{
try {
$composer = $this->getComposer(false);
$composer = $this->getComposer(false, true);
if ($composer) {
$config = $composer->getConfig();
@ -190,6 +191,16 @@ class Application extends BaseApplication
} catch (\Exception $e) {
}
if (defined('PHP_WINDOWS_VERSION_BUILD') && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
$output->writeln('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>');
$output->writeln('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>');
}
if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
$output->writeln('<error>The following exception is caused by a lack of memory and not having swap configured</error>');
$output->writeln('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>');
}
return parent::renderException($exception, $output);
}
@ -219,6 +230,14 @@ class Application extends BaseApplication
return $this->composer;
}
/**
* Removes the cached composer instance
*/
public function resetComposer()
{
$this->composer = null;
}
/**
* @return IOInterface
*/

View File

@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
class DefaultPolicy implements PolicyInterface
{
private $preferStable;
private $preferLowest;
public function __construct($preferStable = false)
public function __construct($preferStable = false, $preferLowest = false)
{
$this->preferStable = $preferStable;
$this->preferLowest = $preferLowest;
}
public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
protected function pruneToBestVersion(Pool $pool, $literals)
{
$operator = $this->preferLowest ? '<' : '>';
$bestLiterals = array($literals[0]);
$bestPackage = $pool->literalToPackage($literals[0]);
foreach ($literals as $i => $literal) {
@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
$package = $pool->literalToPackage($literal);
if ($this->versionCompare($package, $bestPackage, '>')) {
if ($this->versionCompare($package, $bestPackage, $operator)) {
$bestPackage = $package;
$bestLiterals = array($literal);
} elseif ($this->versionCompare($package, $bestPackage, '==')) {

View File

@ -241,7 +241,7 @@ class RuleSetGenerator
if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider) && $package->id <= $provider->id) {
} elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
$reason = ($package->getName() == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
$this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createConflictRule($package, $provider, $reason, $package));
}

View File

@ -155,6 +155,7 @@ class EventDispatcher
$return = 0;
foreach ($listeners as $callable) {
if (!is_string($callable) && is_callable($callable)) {
$event = $this->checkListenerExpectedEvent($callable, $event);
$return = false === call_user_func($callable, $event) ? 1 : 0;
} elseif ($this->isPhpScript($callable)) {
$className = substr($callable, 0, strpos($callable, '::'));
@ -200,9 +201,43 @@ class EventDispatcher
*/
protected function executeEventPhpScript($className, $methodName, Event $event)
{
$event = $this->checkListenerExpectedEvent(array($className, $methodName), $event);
return $className::$methodName($event);
}
/**
* @param mixed $target
* @param Event $event
* @return Event|CommandEvent
*/
protected function checkListenerExpectedEvent($target, Event $event)
{
if (!$event instanceof Script\Event) {
return $event;
}
try {
$reflected = new \ReflectionParameter($target, 0);
} catch (\Exception $e) {
return $event;
}
$typehint = $reflected->getClass();
if (!$typehint instanceof \ReflectionClass) {
return $event;
}
$expected = $typehint->getName();
if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
$event = new CommandEvent($event->getName(), $event->getComposer(), $event->getIO(), $event->isDevMode(), $event->getArguments());
}
return $event;
}
/**
* Add a listener for a particular event
*

View File

@ -107,6 +107,8 @@ class Installer
protected $update = false;
protected $runScripts = true;
protected $ignorePlatformReqs = false;
protected $preferStable = false;
protected $preferLowest = false;
/**
* Array of package names/globs flagged for update
*
@ -307,7 +309,8 @@ class Installer
$aliases,
$this->package->getMinimumStability(),
$this->package->getStabilityFlags(),
$this->package->getPreferStable()
$this->preferStable || $this->package->getPreferStable(),
$this->preferLowest
);
if ($updatedLock) {
$this->io->write('<info>Writing lock file</info>');
@ -694,16 +697,21 @@ class Installer
private function createPolicy()
{
$preferStable = null;
$preferLowest = null;
if (!$this->update && $this->locker->isLocked()) {
$preferStable = $this->locker->getPreferStable();
$preferLowest = $this->locker->getPreferLowest();
}
// old lock file without prefer stable will return null
// old lock file without prefer stable/lowest will return null
// so in this case we use the composer.json info
if (null === $preferStable) {
$preferStable = $this->package->getPreferStable();
$preferStable = $this->preferStable || $this->package->getPreferStable();
}
if (null === $preferLowest) {
$preferLowest = $this->preferLowest;
}
return new DefaultPolicy($preferStable);
return new DefaultPolicy($preferStable, $preferLowest);
}
private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
@ -1244,6 +1252,32 @@ class Installer
return $this;
}
/**
* Should packages be prefered in a stable version when updating?
*
* @param boolean $preferStable
* @return Installer
*/
public function setPreferStable($preferStable = true)
{
$this->preferStable = (boolean) $preferStable;
return $this;
}
/**
* Should packages be prefered in a lowest version when updating?
*
* @param boolean $preferLowest
* @return Installer
*/
public function setPreferLowest($preferLowest = true)
{
$this->preferLowest = (boolean) $preferLowest;
return $this;
}
/**
* Disables plugins.
*

View File

@ -31,7 +31,7 @@ class JsonManipulator
if (!self::$RECURSE_BLOCKS) {
self::$RECURSE_BLOCKS = '(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{[^{}]*\})*\})*\})*\})*';
self::$RECURSE_ARRAYS = '(?:[^\]]*|\[(?:[^\]]*|\[(?:[^\]]*|\[(?:[^\]]*|\[[^\]]*\])*\])*\])*\]|'.self::$RECURSE_BLOCKS.')*';
self::$JSON_STRING = '"(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x09\x0a-\x1f\\\\"])+"';
self::$JSON_STRING = '"(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x09\x0a-\x1f\\\\"])*"';
self::$JSON_VALUE = '(?:[0-9.]+|null|true|false|'.self::$JSON_STRING.'|\['.self::$RECURSE_ARRAYS.'\]|\{'.self::$RECURSE_BLOCKS.'\})';
}
@ -52,7 +52,7 @@ class JsonManipulator
return $this->contents . $this->newline;
}
public function addLink($type, $package, $constraint)
public function addLink($type, $package, $constraint, $sortPackages = false)
{
$decoded = JsonFile::parseJson($this->contents);
@ -90,6 +90,13 @@ class JsonManipulator
}
}
if (true === $sortPackages) {
$requirements = json_decode($links, true);
ksort($requirements);
$links = $this->format($requirements);
}
$this->contents = $matches[1] . $matches[2] . $links . $matches[4];
return true;

View File

@ -137,7 +137,7 @@ class ArchiveManager
$sourcePath = realpath('.');
} else {
// Directory used to download the sources
$sourcePath = sys_get_temp_dir().'/composer_archiver/arch'.uniqid();
$sourcePath = sys_get_temp_dir().'/composer_archive'.uniqid();
$filesystem->ensureDirectoryExists($sourcePath);
// Download sources
@ -154,7 +154,7 @@ class ArchiveManager
}
// Create the archive
$tempTarget = sys_get_temp_dir().'/composer_archiver/arch'.uniqid().'.'.$format;
$tempTarget = sys_get_temp_dir().'/composer_archive'.uniqid().'.'.$format;
$filesystem->ensureDirectoryExists(dirname($tempTarget));
$archivePath = $usableArchiver->archive($sourcePath, $tempTarget, $format, $package->getArchiveExcludes());
@ -164,6 +164,7 @@ class ArchiveManager
if (!$package instanceof RootPackageInterface) {
$filesystem->removeDirectory($sourcePath);
}
$filesystem->remove($tempTarget);
return $target;
}

View File

@ -56,7 +56,6 @@ abstract class BasePackage implements PackageInterface
protected $repository;
protected $transportOptions;
/**
* All descendants' constructors should call this parent constructor
*

View File

@ -194,6 +194,6 @@ class CompletePackage extends Package implements CompletePackageInterface
*/
public function getReplacementPackage()
{
return is_string($this->abandoned)? $this->abandoned : null;
return is_string($this->abandoned) ? $this->abandoned : null;
}
}

View File

@ -182,6 +182,15 @@ class Locker
return isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
}
public function getPreferLowest()
{
$lockData = $this->getLockData();
// return null if not set to allow caller logic to choose the
// right behavior since old lock files have no prefer-lowest
return isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
}
public function getAliases()
{
$lockData = $this->getLockData();
@ -213,10 +222,11 @@ class Locker
* @param string $minimumStability
* @param array $stabilityFlags
* @param bool $preferStable
* @param bool $preferLowest
*
* @return bool
*/
public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable)
public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest)
{
$lock = array(
'_readme' => array('This file locks the dependencies of your project to a known state',
@ -229,6 +239,7 @@ class Locker
'minimum-stability' => $minimumStability,
'stability-flags' => $stabilityFlags,
'prefer-stable' => $preferStable,
'prefer-lowest' => $preferLowest,
);
foreach ($aliases as $package => $versions) {

View File

@ -103,6 +103,11 @@ class VersionParser
$version = $match[1];
}
// ignore build metadata
if (preg_match('{^([^,\s+]+)\+[^\s]+$}', $version, $match)) {
$version = $match[1];
}
// match master-like branches
if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
return '9999999-dev';
@ -178,10 +183,10 @@ class VersionParser
return $this->normalize($name);
}
if (preg_match('#^v?(\d+)(\.(?:\d+|[x*]))?(\.(?:\d+|[x*]))?(\.(?:\d+|[x*]))?$#i', $name, $matches)) {
if (preg_match('#^v?(\d+)(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?$#i', $name, $matches)) {
$version = '';
for ($i = 1; $i < 5; $i++) {
$version .= isset($matches[$i]) ? str_replace('*', 'x', $matches[$i]) : '.x';
$version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
}
return str_replace('x', '9999999', $version).'-dev';
@ -230,11 +235,10 @@ class VersionParser
$constraints = $match[1];
}
$orConstraints = preg_split('{\s*\|\s*}', trim($constraints));
$orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
$orGroups = array();
foreach ($orConstraints as $constraints) {
$andConstraints = preg_split('{\s*,\s*}', $constraints);
$andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
if (count($andConstraints) > 1) {
$constraintObjects = array();
foreach ($andConstraints as $constraint) {
@ -273,16 +277,18 @@ class VersionParser
}
}
if (preg_match('{^[x*](\.[x*])*$}i', $constraint)) {
if (preg_match('{^[xX*](\.[xX*])*$}i', $constraint)) {
return array(new EmptyConstraint);
}
$versionRegex = '(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex;
// match tilde constraints
// like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
// version, to ensure that unstable instances of the current version are allowed.
// however, if a stability suffix is added to the constraint, then a >= match on the current version is
// used instead
if (preg_match('{^~>?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex.'?$}i', $constraint, $matches)) {
if (preg_match('{^~>?'.$versionRegex.'$}i', $constraint, $matches)) {
if (substr($constraint, 0, 2) === '~>') {
throw new \UnexpectedValueException(
'Could not parse version constraint '.$constraint.': '.
@ -329,8 +335,39 @@ class VersionParser
);
}
// match caret constraints
if (preg_match('{^\^'.$versionRegex.'($)}i', $constraint, $matches)) {
// Work out which position in the version we are operating at
if ('0' !== $matches[1] || '' === $matches[2]) {
$position = 1;
} elseif ('0' !== $matches[2] || '' === $matches[3]) {
$position = 2;
} else {
$position = 3;
}
// Calculate the stability suffix
$stabilitySuffix = '';
if (empty($matches[5]) && empty($matches[7])) {
$stabilitySuffix .= '-dev';
}
$lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
$lowerBound = new VersionConstraint('>=', $lowVersion);
// For upper bound, we increment the position of one more significance,
// but highPosition = 0 would be illegal
$highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
$upperBound = new VersionConstraint('<', $highVersion);
return array(
$lowerBound,
$upperBound
);
}
// match wildcard constraints
if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[x*]$}', $constraint, $matches)) {
if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[xX*]$}', $constraint, $matches)) {
if (isset($matches[3]) && '' !== $matches[3]) {
$position = 3;
} elseif (isset($matches[2]) && '' !== $matches[2]) {
@ -352,6 +389,33 @@ class VersionParser
);
}
// match hyphen constraints
if (preg_match('{^(?P<from>'.$versionRegex.') +- +(?P<to>'.$versionRegex.')($)}i', $constraint, $matches)) {
// Calculate the stability suffix
$lowStabilitySuffix = '';
if (empty($matches[6]) && empty($matches[8])) {
$lowStabilitySuffix = '-dev';
}
$lowVersion = $this->normalize($matches['from']);
$lowerBound = new VersionConstraint('>=', $lowVersion . $lowStabilitySuffix);
$highVersion = $matches[10];
if ((!empty($matches[11]) && !empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
$highVersion = $this->normalize($matches['to']);
$upperBound = new VersionConstraint('<=', $highVersion);
} else {
$highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
$highVersion = $this->manipulateVersionString($highMatch, empty($matches[11]) ? 1 : 2, 1) . '-dev';
$upperBound = new VersionConstraint('<', $highVersion);
}
return array(
$lowerBound,
$upperBound
);
}
// match operators constraints
if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
try {
@ -360,7 +424,7 @@ class VersionParser
if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
$version .= '-' . $stabilityModifier;
} elseif ('<' === $matches[1]) {
if (!preg_match('/-stable$/', strtolower($matches[2]))) {
if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
$version .= '-dev';
}
}

View File

@ -103,10 +103,22 @@ class VersionSelector
// attempt to transform 2.1.1 to 2.1
// this allows you to upgrade through minor versions
$semanticVersionParts = explode('.', $version);
$op = '~';
// check to see if we have a semver-looking version
if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) {
// remove the last parts (i.e. the patch version number and any extra)
unset($semanticVersionParts[2], $semanticVersionParts[3]);
if ($semanticVersionParts[0] === '0') {
if ($semanticVersionParts[1] === '0') {
$semanticVersionParts[3] = '*';
} else {
$semanticVersionParts[2] = '*';
unset($semanticVersionParts[3]);
}
$op = '';
} else {
unset($semanticVersionParts[2], $semanticVersionParts[3]);
}
$version = implode('.', $semanticVersionParts);
} else {
return $prettyVersion;
@ -118,7 +130,7 @@ class VersionSelector
}
// 2.1 -> ~2.1
return '~'.$version;
return $op.$version;
}
private function getParser()

View File

@ -28,12 +28,13 @@ use Composer\DependencyResolver\Pool;
* Plugin manager
*
* @author Nils Adermann <naderman@naderman.de>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PluginManager
{
protected $composer;
protected $io;
protected $globalRepository;
protected $globalComposer;
protected $versionParser;
protected $plugins = array();
@ -44,15 +45,15 @@ class PluginManager
/**
* Initializes plugin manager
*
* @param Composer $composer
* @param IOInterface $io
* @param RepositoryInterface $globalRepository
* @param Composer $composer
* @param Composer $globalComposer
*/
public function __construct(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null)
{
$this->composer = $composer;
$this->io = $io;
$this->globalRepository = $globalRepository;
$this->composer = $composer;
$this->globalComposer = $globalComposer;
$this->versionParser = new VersionParser();
}
@ -62,12 +63,12 @@ class PluginManager
public function loadInstalledPlugins()
{
$repo = $this->composer->getRepositoryManager()->getLocalRepository();
$globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
if ($repo) {
$this->loadRepository($repo);
}
if ($this->globalRepository) {
$this->loadRepository($this->globalRepository);
if ($globalRepo) {
$this->loadRepository($globalRepo);
}
}
@ -206,11 +207,13 @@ class PluginManager
}
$classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']);
$pool = new Pool('dev');
$localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
$globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
$pool = new Pool('dev');
$pool->addRepository($localRepo);
if ($this->globalRepository) {
$pool->addRepository($this->globalRepository);
if ($globalRepo) {
$pool->addRepository($globalRepo);
}
$autoloadPackages = array($package->getName() => $package);
@ -219,7 +222,7 @@ class PluginManager
$generator = $this->composer->getAutoloadGenerator();
$autoloads = array();
foreach ($autoloadPackages as $autoloadPackage) {
$downloadPath = $this->getInstallPath($autoloadPackage, ($this->globalRepository && $this->globalRepository->hasPackage($autoloadPackage)));
$downloadPath = $this->getInstallPath($autoloadPackage, ($globalRepo && $globalRepo->hasPackage($autoloadPackage)));
$autoloads[] = array($autoloadPackage, $downloadPath);
}
@ -261,9 +264,6 @@ class PluginManager
return $this->composer->getInstallationManager()->getInstallPath($package);
}
$targetDir = $package->getTargetDir();
$vendorDir = $this->composer->getConfig()->get('home').'/vendor';
return ($vendorDir ? $vendorDir.'/' : '').$package->getPrettyName().($targetDir ? '/'.$targetDir : '');
return $this->globalComposer->getInstallationManager()->getInstallPath($package);
}
}

View File

@ -85,7 +85,7 @@ class ComposerRepository extends ArrayRepository
$this->config = $config;
$this->options = $repoConfig['options'];
$this->url = $repoConfig['url'];
$this->baseUrl = rtrim(preg_replace('{^(.*)(?:/packages.json)?(?:[?#].*)?$}', '$1', $this->url), '/');
$this->baseUrl = rtrim(preg_replace('{^(.*)(?:/[^/\\]+.json)?(?:[?#].*)?$}', '$1', $this->url), '/');
$this->io = $io;
$this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
$this->loader = new ArrayLoader();
@ -395,7 +395,7 @@ class ComposerRepository extends ArrayRepository
$jsonUrlParts = parse_url($this->url);
if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '/packages.json')) {
if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
$jsonUrl = $this->url;
} else {
$jsonUrl = $this->url . '/packages.json';

View File

@ -93,7 +93,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
$composer = JsonFile::parseJson($composer, $resource);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
$changeset = JsonFile::parseJson($this->getContents($resource), $resource);
$composer['time'] = $changeset['timestamp'];

View File

@ -156,7 +156,7 @@ class GitDriver extends VcsDriver
$composer = JsonFile::parseJson($composer, $resource);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$this->process->execute(sprintf('git log -1 --format=%%at %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir);
$date = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
$composer['time'] = $date->format('Y-m-d H:i:s');
@ -227,7 +227,7 @@ class GitDriver extends VcsDriver
if (Filesystem::isLocalPath($url)) {
$url = Filesystem::getPlatformPath($url);
if (!is_dir($url)) {
throw new \RuntimeException('Directory does not exist: '.$url);
return false;
}
$process = new ProcessExecutor($io);

View File

@ -171,7 +171,7 @@ class GitHubDriver extends VcsDriver
if ($composer) {
$composer = JsonFile::parseJson($composer, $resource);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
$commit = JsonFile::parseJson($this->getContents($resource), $resource);
$composer['time'] = $commit['commit']['committer']['date'];

View File

@ -102,7 +102,7 @@ class HgBitbucketDriver extends VcsDriver
$composer = JsonFile::parseJson($repoData['data'], $resource);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
$changeset = JsonFile::parseJson($this->getContents($resource), $resource);
$composer['time'] = $changeset['timestamp'];

View File

@ -124,7 +124,7 @@ class HgDriver extends VcsDriver
$composer = JsonFile::parseJson($composer, $identifier);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$this->process->execute(sprintf('hg log --template "{date|rfc3339date}" -r %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir);
$date = new \DateTime(trim($output), new \DateTimeZone('UTC'));
$composer['time'] = $date->format('Y-m-d H:i:s');
@ -200,7 +200,7 @@ class HgDriver extends VcsDriver
if (Filesystem::isLocalPath($url)) {
$url = Filesystem::getPlatformPath($url);
if (!is_dir($url)) {
throw new \RuntimeException('Directory does not exist: '.$url);
return false;
}
$process = new ProcessExecutor();

View File

@ -148,7 +148,7 @@ class SvnDriver extends VcsDriver
$composer = JsonFile::parseJson($output, $this->baseUrl . $resource . $rev);
if (!isset($composer['time'])) {
if (empty($composer['time'])) {
$output = $this->execute('svn info', $this->baseUrl . $path . $rev);
foreach ($this->process->splitLines($output) as $line) {
if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {

View File

@ -385,15 +385,19 @@ class RemoteFilesystem
protected function getOptionsForUrl($originUrl, $additionalOptions)
{
if (defined('HHVM_VERSION')) {
$phpVersion = 'HHVM ' . HHVM_VERSION;
} else {
$phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
}
$headers = array(
sprintf(
'User-Agent: Composer/%s (%s; %s; PHP %s.%s.%s)',
'User-Agent: Composer/%s (%s; %s; %s)',
Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION,
php_uname('s'),
php_uname('r'),
PHP_MAJOR_VERSION,
PHP_MINOR_VERSION,
PHP_RELEASE_VERSION
$phpVersion
)
);

View File

@ -43,6 +43,19 @@ final class StreamContextFactory
$proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
}
// Override with HTTPS proxy if present and URL is https
if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
$proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
}
// Remove proxy if URL matches no_proxy directive
if (!empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
$pattern = new NoProxyPattern($_SERVER['no_proxy']);
if ($pattern->test($url)) {
unset($proxy);
}
}
if (!empty($proxy)) {
$proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
$proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
@ -64,48 +77,46 @@ final class StreamContextFactory
$options['http']['proxy'] = $proxyURL;
// Handle no_proxy directive
if (!empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
$pattern = new NoProxyPattern($_SERVER['no_proxy']);
if ($pattern->test($url)) {
unset($options['http']['proxy']);
// enabled request_fulluri unless it is explicitly disabled
switch (parse_url($url, PHP_URL_SCHEME)) {
case 'http': // default request_fulluri to true
$reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
$options['http']['request_fulluri'] = true;
}
break;
case 'https': // default request_fulluri to true
$reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
$options['http']['request_fulluri'] = true;
}
break;
}
// add SNI opts for https URLs
if ('https' === parse_url($url, PHP_URL_SCHEME)) {
$options['ssl']['SNI_enabled'] = true;
if (version_compare(PHP_VERSION, '5.6.0', '<')) {
$options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
}
}
// add request_fulluri and authentication if we still have a proxy to connect to
if (!empty($options['http']['proxy'])) {
// enabled request_fulluri unless it is explicitly disabled
switch (parse_url($url, PHP_URL_SCHEME)) {
case 'http': // default request_fulluri to true
$reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
$options['http']['request_fulluri'] = true;
}
break;
case 'https': // default request_fulluri to true
$reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
$options['http']['request_fulluri'] = true;
}
break;
// handle proxy auth if present
if (isset($proxy['user'])) {
$auth = urldecode($proxy['user']);
if (isset($proxy['pass'])) {
$auth .= ':' . urldecode($proxy['pass']);
}
$auth = base64_encode($auth);
if (isset($proxy['user'])) {
$auth = urldecode($proxy['user']);
if (isset($proxy['pass'])) {
$auth .= ':' . urldecode($proxy['pass']);
}
$auth = base64_encode($auth);
// Preserve headers if already set in default options
if (isset($defaultOptions['http']['header'])) {
if (is_string($defaultOptions['http']['header'])) {
$defaultOptions['http']['header'] = array($defaultOptions['http']['header']);
}
$defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
} else {
$options['http']['header'] = array("Proxy-Authorization: Basic {$auth}");
// Preserve headers if already set in default options
if (isset($defaultOptions['http']['header'])) {
if (is_string($defaultOptions['http']['header'])) {
$defaultOptions['http']['header'] = array($defaultOptions['http']['header']);
}
$defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
} else {
$options['http']['header'] = array("Proxy-Authorization: Basic {$auth}");
}
}
}

View File

@ -63,7 +63,7 @@ class AllFunctionalTest extends \PHPUnit_Framework_TestCase
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Building the phar does not work on HHVM.');
}
$fs = new Filesystem;
$fs->removeDirectory(dirname(self::$pharPath));
$fs->ensureDirectoryExists(dirname(self::$pharPath));

View File

@ -21,6 +21,10 @@ class CacheTest extends TestCase
public function setUp()
{
if (getenv('TRAVIS')) {
$this->markTestSkipped('Test causes intermittent failures on Travis');
}
$this->root = sys_get_temp_dir() . '/composer_testdir';
$this->ensureDirectoryExistsAndClear($this->root);

View File

@ -247,4 +247,20 @@ class DefaultPolicyTest extends TestCase
return $map;
}
public function testSelectLowest()
{
$policy = new DefaultPolicy(false, true);
$this->repo->addPackage($packageA1 = $this->getPackage('A', '1.0'));
$this->repo->addPackage($packageA2 = $this->getPackage('A', '2.0'));
$this->pool->addRepository($this->repo);
$literals = array($packageA1->getId(), $packageA2->getId());
$expected = array($packageA1->getId());
$selected = $policy->selectPreferedPackages($this->pool, array(), $literals);
$this->assertEquals($expected, $selected);
}
}

View File

@ -17,6 +17,7 @@ use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallerEvents;
use Composer\TestCase;
use Composer\Script\ScriptEvents;
use Composer\Script\CommandEvent;
use Composer\Util\ProcessExecutor;
class EventDispatcherTest extends TestCase
@ -28,7 +29,7 @@ class EventDispatcherTest extends TestCase
{
$io = $this->getMock('Composer\IO\IOInterface');
$dispatcher = $this->getDispatcherStubForListenersTest(array(
"Composer\Test\EventDispatcher\EventDispatcherTest::call"
'Composer\Test\EventDispatcher\EventDispatcherTest::call'
), $io);
$io->expects($this->once())
@ -38,6 +39,26 @@ class EventDispatcherTest extends TestCase
$dispatcher->dispatchCommandEvent(ScriptEvents::POST_INSTALL_CMD, false);
}
public function testDispatcherCanConvertScriptEventToCommandEventForListener()
{
$io = $this->getMock('Composer\IO\IOInterface');
$dispatcher = $this->getDispatcherStubForListenersTest(array(
'Composer\Test\EventDispatcher\EventDispatcherTest::expectsCommandEvent'
), $io);
$this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false));
}
public function testDispatcherDoesNotAttemptConversionForListenerWithoutTypehint()
{
$io = $this->getMock('Composer\IO\IOInterface');
$dispatcher = $this->getDispatcherStubForListenersTest(array(
'Composer\Test\EventDispatcher\EventDispatcherTest::expectsVariableEvent'
), $io);
$this->assertEquals(1, $dispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, false));
}
/**
* @dataProvider getValidCommands
* @param string $command
@ -205,6 +226,16 @@ class EventDispatcherTest extends TestCase
throw new \RuntimeException();
}
public static function expectsCommandEvent(CommandEvent $event)
{
return false;
}
public static function expectsVariableEvent($event)
{
return false;
}
public static function someMethod()
{
return true;

View File

@ -48,6 +48,7 @@ install --prefer-dist
"a/a": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -25,7 +25,8 @@ Requirements from the composer file are not installed if the lock file is presen
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--RUN--
install

View File

@ -0,0 +1,44 @@
--TEST--
Install from a lock file that deleted a package
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "whitelisted", "version": "1.1.0" },
{ "name": "whitelisted", "version": "1.0.0", "require": { "fixed-dependency": "1.0.0", "old-dependency": "1.0.0" } },
{ "name": "fixed-dependency", "version": "1.1.0" },
{ "name": "fixed-dependency", "version": "1.0.0" },
{ "name": "old-dependency", "version": "1.0.0" }
]
}
],
"require": {
"whitelisted": "1.*",
"fixed-dependency": "1.*"
}
}
--LOCK--
{
"packages": [
{ "name": "whitelisted", "version": "1.1.0" },
{ "name": "fixed-dependency", "version": "1.0.0" }
],
"packages-dev": null,
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false
}
--INSTALLED--
[
{ "name": "whitelisted", "version": "1.0.0", "require": { "old-dependency": "1.0.0", "fixed-dependency": "1.0.0" } },
{ "name": "fixed-dependency", "version": "1.0.0" },
{ "name": "old-dependency", "version": "1.0.0" }
]
--RUN--
install
--EXPECT--
Uninstalling old-dependency (1.0.0)
Updating whitelisted (1.0.0) to whitelisted (1.1.0)

View File

@ -33,7 +33,8 @@ Installing an old alias that doesn't exist anymore from a lock is possible
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--RUN--
install

View File

@ -36,6 +36,7 @@ Partial update from lock file should apply lock file and downgrade unstable pack
"b/unstable": 15
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
@ -59,6 +60,7 @@ update c/uptodate
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -36,6 +36,7 @@ Partial update from lock file should update everything to the state of the lock,
"b/unstable": 15
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}
@ -59,6 +60,7 @@ update b/unstable
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -43,6 +43,7 @@ update b/unstable
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -39,7 +39,8 @@ Update aliased package does not mess up the lock file
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--INSTALLED--
[
@ -66,6 +67,7 @@ update
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -0,0 +1,40 @@
--TEST--
Updates packages to their lowest stable version
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "a/a", "version": "1.0.0-rc1" },
{ "name": "a/a", "version": "1.0.1" },
{ "name": "a/a", "version": "1.1.0" },
{ "name": "a/b", "version": "1.0.0" },
{ "name": "a/b", "version": "1.0.1" },
{ "name": "a/b", "version": "2.0.0" },
{ "name": "a/c", "version": "1.0.0" },
{ "name": "a/c", "version": "2.0.0" }
]
}
],
"require": {
"a/a": "~1.0@dev",
"a/c": "2.*"
},
"require-dev": {
"a/b": "*"
}
}
--INSTALLED--
[
{ "name": "a/a", "version": "1.0.0-rc1" },
{ "name": "a/c", "version": "2.0.0" },
{ "name": "a/b", "version": "1.0.1" }
]
--RUN--
update --prefer-lowest --prefer-stable
--EXPECT--
Updating a/a (1.0.0-rc1) to a/a (1.0.1)
Updating a/b (1.0.1) to a/b (1.0.0)

View File

@ -32,7 +32,8 @@ Limited update takes rules from lock if available, and not from the installed re
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--INSTALLED--
[

View File

@ -0,0 +1,32 @@
--TEST--
Update with a package whitelist removes unused packages
--COMPOSER--
{
"repositories": [
{
"type": "package",
"package": [
{ "name": "whitelisted", "version": "1.1.0" },
{ "name": "whitelisted", "version": "1.0.0", "require": { "fixed-dependency": "1.0.0", "old-dependency": "1.0.0" } },
{ "name": "fixed-dependency", "version": "1.1.0" },
{ "name": "fixed-dependency", "version": "1.0.0" },
{ "name": "old-dependency", "version": "1.0.0" }
]
}
],
"require": {
"whitelisted": "1.*",
"fixed-dependency": "1.*"
}
}
--INSTALLED--
[
{ "name": "whitelisted", "version": "1.0.0", "require": { "old-dependency": "1.0.0", "fixed-dependency": "1.0.0" } },
{ "name": "fixed-dependency", "version": "1.0.0" },
{ "name": "old-dependency", "version": "1.0.0" }
]
--RUN--
update --with-dependencies whitelisted
--EXPECT--
Uninstalling old-dependency (1.0.0)
Updating whitelisted (1.0.0) to whitelisted (1.1.0)

View File

@ -20,7 +20,8 @@ Installing locked dev packages should remove old dependencies
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--INSTALLED--
[

View File

@ -32,7 +32,8 @@ Updating a dev package for new reference updates the url and reference
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {"a/a":20},
"prefer-stable": false
"prefer-stable": false,
"prefer-lowest": false
}
--INSTALLED--
[
@ -59,6 +60,7 @@ update
"minimum-stability": "dev",
"stability-flags": {"a/a":20},
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@ -217,6 +217,8 @@ class InstallerTest extends TestCase
->setDryRun($input->getOption('dry-run'))
->setUpdateWhitelist($input->getArgument('packages'))
->setWhitelistDependencies($input->getOption('with-dependencies'))
->setPreferStable($input->getOption('prefer-stable'))
->setPreferLowest($input->getOption('prefer-lowest'))
->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'));
return $installer->run();
@ -292,7 +294,7 @@ class InstallerTest extends TestCase
// Change paths like file://foobar to file:///path/to/fixtures
if (preg_match('{^file://[^/]}', $repo['url'])) {
$repo['url'] = "file://${fixturesDir}/" . substr($repo['url'], 7);
$repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
}
unset($repo);

View File

@ -73,6 +73,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
),
array(
'{
"empty": "",
"require": {
"foo": "bar"
}
@ -81,6 +82,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
'vendor/baz',
'qux',
'{
"empty": "",
"require": {
"foo": "bar",
"vendor/baz": "qux"
@ -281,6 +283,58 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
);
}
/**
* @dataProvider providerAddLinkAndSortPackages
*/
public function testAddLinkAndSortPackages($json, $type, $package, $constraint, $sortPackages, $expected)
{
$manipulator = new JsonManipulator($json);
$this->assertTrue($manipulator->addLink($type, $package, $constraint, $sortPackages));
$this->assertEquals($expected, $manipulator->getContents());
}
public function providerAddLinkAndSortPackages()
{
return array(
array(
'{
"require": {
"vendor/baz": "qux"
}
}',
'require',
'foo',
'bar',
true,
'{
"require": {
"foo": "bar",
"vendor/baz": "qux"
}
}
'
),
array(
'{
"require": {
"vendor/baz": "qux"
}
}',
'require',
'foo',
'bar',
false,
'{
"require": {
"vendor/baz": "qux",
"foo": "bar"
}
}
'
),
);
}
/**
* @dataProvider removeSubNodeProvider
*/

View File

@ -16,6 +16,7 @@ use Composer\Config;
use Composer\Factory;
use Composer\Repository;
use Composer\Repository\RepositoryManager;
use Composer\Repository\WritableRepositoryInterface;
use Composer\Installer;
use Composer\IO\IOInterface;
@ -46,7 +47,7 @@ class FactoryMock extends Factory
{
}
protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im)
protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
{
}
}

View File

@ -135,9 +135,10 @@ class LockerTest extends \PHPUnit_Framework_TestCase
'platform' => array(),
'platform-dev' => array(),
'prefer-stable' => false,
'prefer-lowest' => false,
));
$locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false);
$locker->setLockData(array($package1, $package2), array(), array(), array(), array(), 'dev', array(), false, false);
}
public function testLockBadPackages()
@ -156,7 +157,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
$this->setExpectedException('LogicException');
$locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false);
$locker->setLockData(array($package1), array(), array(), array(), array(), 'dev', array(), false, false);
}
public function testIsFresh()

View File

@ -90,6 +90,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'forces w.x.y.z/2' => array('0', '0.0.0.0'),
'parses long' => array('10.4.13-beta', '10.4.13.0-beta'),
'parses long/2' => array('10.4.13beta2', '10.4.13.0-beta2'),
'parses long/semver' => array('10.4.13beta.2', '10.4.13.0-beta2'),
'expand shorthand' => array('10.4.13-b', '10.4.13.0-beta'),
'expand shorthand2' => array('10.4.13-b5', '10.4.13.0-beta5'),
'strips leading v' => array('v1.0.0', '1.0.0.0'),
@ -109,6 +110,10 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'parses arbitrary2' => array('DEV-FOOBAR', 'dev-FOOBAR'),
'parses arbitrary3' => array('dev-feature/foo', 'dev-feature/foo'),
'ignores aliases' => array('dev-master as 1.0.0', '9999999-dev'),
'semver metadata' => array('dev-master+foo.bar', '9999999-dev'),
'semver metadata/2' => array('1.0.0-beta.5+foo', '1.0.0.0-beta5'),
'semver metadata/3' => array('1.0.0+foo', '1.0.0.0'),
'metadata w/ alias' => array('1.0.0+foo as 2.0', '1.0.0.0'),
);
}
@ -130,6 +135,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'invalid type' => array('1.0.0-meh'),
'too many bits' => array('1.0.0.0.0'),
'non-dev arbitrary' => array('feature-foo'),
'metadata w/ space' => array('1.0.0+foo bar'),
);
}
@ -208,7 +214,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'match any' => array('*', new EmptyConstraint()),
'match any/2' => array('*.*', new EmptyConstraint()),
'match any/3' => array('*.x.*', new EmptyConstraint()),
'match any/4' => array('x.x.x.*', new EmptyConstraint()),
'match any/4' => array('x.X.x.*', new EmptyConstraint()),
'not equal' => array('<>1.0.0', new VersionConstraint('<>', '1.0.0.0')),
'not equal/2' => array('!=1.0.0', new VersionConstraint('!=', '1.0.0.0')),
'greater than' => array('>1.0.0', new VersionConstraint('>', '1.0.0.0')),
@ -221,6 +227,8 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
'completes version' => array('=1.0', new VersionConstraint('=', '1.0.0.0')),
'shorthand beta' => array('1.2.3b5', new VersionConstraint('=', '1.2.3.0-beta5')),
'accepts spaces' => array('>= 1.2.3', new VersionConstraint('>=', '1.2.3.0')),
'accepts spaces/2' => array('< 1.2.3', new VersionConstraint('<', '1.2.3.0-dev')),
'accepts spaces/3' => array('> 1.2.3', new VersionConstraint('>', '1.2.3.0')),
'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')),
@ -253,7 +261,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
array('20.*', new VersionConstraint('>=', '20.0.0.0-dev'), new VersionConstraint('<', '21.0.0.0-dev')),
array('2.0.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.1.0.0-dev')),
array('2.2.x', new VersionConstraint('>=', '2.2.0.0-dev'), new VersionConstraint('<', '2.3.0.0-dev')),
array('2.10.x', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.11.0.0-dev')),
array('2.10.X', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.11.0.0-dev')),
array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0-dev'), new VersionConstraint('<', '2.1.4.0-dev')),
array('0.*', null, new VersionConstraint('<', '1.0.0.0-dev')),
);
@ -291,16 +299,112 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
);
}
public function testParseConstraintsMulti()
/**
* @dataProvider caretConstraints
*/
public function testParseCaretWildcard($input, $min, $max)
{
$parser = new VersionParser;
if ($min) {
$expected = new MultiConstraint(array($min, $max));
} else {
$expected = $max;
}
$this->assertSame((string) $expected, (string) $parser->parseConstraints($input));
}
public function caretConstraints()
{
return array(
array('^1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')),
array('^0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '1.0.0.0-dev')),
array('^0.0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '0.1.0.0-dev')),
array('^1.2', new VersionConstraint('>=', '1.2.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')),
array('^1.2.3-beta.2', new VersionConstraint('>=', '1.2.3.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')),
array('^1.2.3.4', new VersionConstraint('>=', '1.2.3.4-dev'), new VersionConstraint('<', '2.0.0.0-dev')),
array('^1.2.3', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')),
array('^0.2.3', new VersionConstraint('>=', '0.2.3.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')),
array('^0.2', new VersionConstraint('>=', '0.2.0.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')),
array('^0.0.3', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')),
array('^0.0.3-alpha', new VersionConstraint('>=', '0.0.3.0-alpha'), new VersionConstraint('<', '0.0.4.0-dev')),
array('^0.0.3-dev', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')),
);
}
/**
* @dataProvider hyphenConstraints
*/
public function testParseHyphen($input, $min, $max)
{
$parser = new VersionParser;
if ($min) {
$expected = new MultiConstraint(array($min, $max));
} else {
$expected = $max;
}
$this->assertSame((string) $expected, (string) $parser->parseConstraints($input));
}
public function hyphenConstraints()
{
return array(
array('1 - 2', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '3.0.0.0-dev')),
array('1.2.3 - 2.3.4.5', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<=', '2.3.4.5')),
array('1.2-beta - 2.3', new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.4.0.0-dev')),
array('1.2-beta - 2.3-dev', new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<=', '2.3.0.0-dev')),
array('1.2-RC - 2.3.1', new VersionConstraint('>=', '1.2.0.0-RC'), new VersionConstraint('<=', '2.3.1.0')),
array('1.2.3-alpha - 2.3-RC', new VersionConstraint('>=', '1.2.3.0-alpha'), new VersionConstraint('<=', '2.3.0.0-RC')),
);
}
/**
* @dataProvider multiConstraintProvider
*/
public function testParseConstraintsMulti($constraint)
{
$parser = new VersionParser;
$first = new VersionConstraint('>', '2.0.0.0');
$second = new VersionConstraint('<=', '3.0.0.0');
$multi = new MultiConstraint(array($first, $second));
$this->assertSame((string) $multi, (string) $parser->parseConstraints('>2.0,<=3.0'));
$this->assertSame((string) $multi, (string) $parser->parseConstraints($constraint));
}
public function testParseConstraintsMultiDisjunctiveHasPrioOverConjuctive()
public function multiConstraintProvider()
{
return array(
array('>2.0,<=3.0'),
array('>2.0 <=3.0'),
array('>2.0 <=3.0'),
array('>2.0, <=3.0'),
array('>2.0 ,<=3.0'),
array('>2.0 , <=3.0'),
array('>2.0 , <=3.0'),
array('> 2.0 <= 3.0'),
array('> 2.0 , <= 3.0'),
array(' > 2.0 , <= 3.0 '),
);
}
public function testParseConstraintsMultiWithStabilitySuffix()
{
$parser = new VersionParser;
$first = new VersionConstraint('>=', '1.1.0.0-alpha4');
$second = new VersionConstraint('<', '1.2.9999999.9999999-dev');
$multi = new MultiConstraint(array($first, $second));
$this->assertSame((string) $multi, (string) $parser->parseConstraints('>=1.1.0-alpha4,<1.2.x-dev'));
$first = new VersionConstraint('>=', '1.1.0.0-alpha4');
$second = new VersionConstraint('<', '1.2.0.0-beta2');
$multi = new MultiConstraint(array($first, $second));
$this->assertSame((string) $multi, (string) $parser->parseConstraints('>=1.1.0-alpha4,<1.2-beta2'));
}
/**
* @dataProvider multiConstraintProvider2
*/
public function testParseConstraintsMultiDisjunctiveHasPrioOverConjuctive($constraint)
{
$parser = new VersionParser;
$first = new VersionConstraint('>', '2.0.0.0');
@ -308,7 +412,16 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
$third = new VersionConstraint('>', '2.0.6.0');
$multi1 = new MultiConstraint(array($first, $second));
$multi2 = new MultiConstraint(array($multi1, $third), false);
$this->assertSame((string) $multi2, (string) $parser->parseConstraints('>2.0,<2.0.5 | >2.0.6'));
$this->assertSame((string) $multi2, (string) $parser->parseConstraints($constraint));
}
public function multiConstraintProvider2()
{
return array(
array('>2.0,<2.0.5 | >2.0.6'),
array('>2.0,<2.0.5 || >2.0.6'),
array('> 2.0 , <2.0.5 | > 2.0.6'),
);
}
public function testParseConstraintsMultiWithStabilities()
@ -335,6 +448,9 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
return array(
'empty ' => array(''),
'invalid version' => array('1.0.0-meh'),
'operator abuse' => array('>2.0,,<=3.0'),
'operator abuse/2' => array('>2.0 ,, <=3.0'),
'operator abuse/3' => array('>2.0 ||| <=3.0'),
);
}

View File

@ -98,7 +98,10 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
array('v1.2.1', false, 'stable', '~1.2'),
array('3.1.2-pl2', false, 'stable', '~3.1'),
array('3.1.2-patch', false, 'stable', '~3.1'),
// for non-stable versions, we add ~, but don't try the (1.2.1 -> 1.2) transformation
array('0.1.0', false, 'stable', '0.1.*'),
array('0.1.3', false, 'stable', '0.1.*'),
array('0.0.3', false, 'stable', '0.0.3.*'),
array('0.0.3-alpha', false, 'alpha', '0.0.3.*@alpha'),
array('2.0-beta.1', false, 'beta', '~2.0@beta'),
array('3.1.2-alpha5', false, 'alpha', '~3.1@alpha'),
array('3.0-RC2', false, 'RC', '~3.0@RC'),

View File

@ -76,7 +76,7 @@ class PluginInstallerTest extends \PHPUnit_Framework_TestCase
$this->composer->setInstallationManager($im);
$this->composer->setAutoloadGenerator($this->autoloadGenerator);
$this->pm = new PluginManager($this->composer, $this->io);
$this->pm = new PluginManager($this->io, $this->composer);
$this->composer->setPluginManager($this->pm);
$config->merge(array(

View File

@ -20,6 +20,8 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
{
unset($_SERVER['HTTP_PROXY']);
unset($_SERVER['http_proxy']);
unset($_SERVER['HTTPS_PROXY']);
unset($_SERVER['https_proxy']);
unset($_SERVER['no_proxy']);
}
@ -27,6 +29,8 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
{
unset($_SERVER['HTTP_PROXY']);
unset($_SERVER['http_proxy']);
unset($_SERVER['HTTPS_PROXY']);
unset($_SERVER['https_proxy']);
unset($_SERVER['no_proxy']);
}
@ -126,17 +130,56 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
{
$_SERVER['http_proxy'] = 'http://username:password@proxyserver.net';
$context = StreamContextFactory::getContext('http://example.org', array('http' => array('method' => 'GET')));
$context = StreamContextFactory::getContext('https://example.org', array('http' => array('method' => 'GET')));
$options = stream_context_get_options($context);
$this->assertEquals(array('http' => array(
'proxy' => 'tcp://proxyserver.net:80',
'request_fulluri' => true,
'method' => 'GET',
'header' => array("Proxy-Authorization: Basic " . base64_encode('username:password')),
'max_redirects' => 20,
'follow_location' => 1,
)), $options);
$expected = array(
'http' => array(
'proxy' => 'tcp://proxyserver.net:80',
'request_fulluri' => true,
'method' => 'GET',
'header' => array("Proxy-Authorization: Basic " . base64_encode('username:password')),
'max_redirects' => 20,
'follow_location' => 1,
), 'ssl' => array(
'SNI_enabled' => true,
'SNI_server_name' => 'example.org'
)
);
if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
unset($expected['ssl']['SNI_server_name']);
}
$this->assertEquals($expected, $options);
}
public function testHttpsProxyOverride()
{
if (!extension_loaded('openssl')) {
$this->markTestSkipped('Requires openssl');
}
$_SERVER['http_proxy'] = 'http://username:password@proxyserver.net';
$_SERVER['https_proxy'] = 'https://woopproxy.net';
$context = StreamContextFactory::getContext('https://example.org', array('http' => array('method' => 'GET')));
$options = stream_context_get_options($context);
$expected = array(
'http' => array(
'proxy' => 'ssl://woopproxy.net:443',
'request_fulluri' => true,
'method' => 'GET',
'max_redirects' => 20,
'follow_location' => 1,
), 'ssl' => array(
'SNI_enabled' => true,
'SNI_server_name' => 'example.org'
)
);
if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
unset($expected['ssl']['SNI_server_name']);
}
$this->assertEquals($expected, $options);
}
/**