Browse Source

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

Conflicts:
	src/Composer/Factory.php
Nicolas Toniazzi 10 years ago
parent
commit
ad9c3d3b30
65 changed files with 1060 additions and 236 deletions
  1. 30 0
      CHANGELOG.md
  2. 91 43
      composer.lock
  3. 12 17
      doc/00-intro.md
  4. 18 31
      doc/01-basic-usage.md
  5. 12 1
      doc/03-cli.md
  6. 2 2
      doc/04-schema.md
  7. 59 0
      doc/articles/http-basic-authentication.md
  8. 1 0
      src/Composer/Autoload/ClassLoader.php
  9. 9 0
      src/Composer/Command/Command.php
  10. 8 2
      src/Composer/Command/CreateProjectCommand.php
  11. 1 1
      src/Composer/Command/DiagnoseCommand.php
  12. 2 1
      src/Composer/Command/InitCommand.php
  13. 50 3
      src/Composer/Command/LicensesCommand.php
  14. 23 9
      src/Composer/Command/RequireCommand.php
  15. 2 1
      src/Composer/Command/SelfUpdateCommand.php
  16. 4 0
      src/Composer/Command/UpdateCommand.php
  17. 11 1
      src/Composer/Compiler.php
  18. 1 1
      src/Composer/Config.php
  19. 21 2
      src/Composer/Console/Application.php
  20. 5 2
      src/Composer/DependencyResolver/DefaultPolicy.php
  21. 1 1
      src/Composer/DependencyResolver/RuleSetGenerator.php
  22. 35 0
      src/Composer/EventDispatcher/EventDispatcher.php
  23. 38 4
      src/Composer/Installer.php
  24. 9 2
      src/Composer/Json/JsonManipulator.php
  25. 3 2
      src/Composer/Package/Archiver/ArchiveManager.php
  26. 0 1
      src/Composer/Package/BasePackage.php
  27. 1 1
      src/Composer/Package/CompletePackage.php
  28. 12 1
      src/Composer/Package/Locker.php
  29. 73 9
      src/Composer/Package/Version/VersionParser.php
  30. 14 2
      src/Composer/Package/Version/VersionSelector.php
  31. 17 17
      src/Composer/Plugin/PluginManager.php
  32. 2 2
      src/Composer/Repository/ComposerRepository.php
  33. 1 1
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  34. 2 2
      src/Composer/Repository/Vcs/GitDriver.php
  35. 1 1
      src/Composer/Repository/Vcs/GitHubDriver.php
  36. 1 1
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  37. 2 2
      src/Composer/Repository/Vcs/HgDriver.php
  38. 1 1
      src/Composer/Repository/Vcs/SvnDriver.php
  39. 8 4
      src/Composer/Util/RemoteFilesystem.php
  40. 47 36
      src/Composer/Util/StreamContextFactory.php
  41. 1 1
      tests/Composer/Test/AllFunctionalTest.php
  42. 4 0
      tests/Composer/Test/CacheTest.php
  43. 16 0
      tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php
  44. 32 1
      tests/Composer/Test/EventDispatcher/EventDispatcherTest.php
  45. 1 0
      tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test
  46. 2 1
      tests/Composer/Test/Fixtures/installer/install-from-empty-lock.test
  47. 44 0
      tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test
  48. 2 1
      tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test
  49. 2 0
      tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test
  50. 2 0
      tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test
  51. 1 0
      tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test
  52. 3 1
      tests/Composer/Test/Fixtures/installer/update-alias-lock.test
  53. 40 0
      tests/Composer/Test/Fixtures/installer/update-prefer-lowest-stable.test
  54. 2 1
      tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test
  55. 32 0
      tests/Composer/Test/Fixtures/installer/update-whitelist-removes-unused.test
  56. 2 1
      tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test
  57. 3 1
      tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test
  58. 3 1
      tests/Composer/Test/InstallerTest.php
  59. 54 0
      tests/Composer/Test/Json/JsonManipulatorTest.php
  60. 2 1
      tests/Composer/Test/Mock/FactoryMock.php
  61. 3 2
      tests/Composer/Test/Package/LockerTest.php
  62. 122 6
      tests/Composer/Test/Package/Version/VersionParserTest.php
  63. 4 1
      tests/Composer/Test/Package/Version/VersionSelectorTest.php
  64. 1 1
      tests/Composer/Test/Plugin/PluginInstallerTest.php
  65. 52 9
      tests/Composer/Test/Util/StreamContextFactoryTest.php

+ 30 - 0
CHANGELOG.md

@@ -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

+ 91 - 43
composer.lock

@@ -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": [],

+ 12 - 17
doc/00-intro.md

@@ -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

+ 18 - 31
doc/01-basic-usage.md

@@ -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.

+ 12 - 1
doc/03-cli.md

@@ -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,

+ 2 - 2
doc/04-schema.md

@@ -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
 

+ 59 - 0
doc/articles/http-basic-authentication.md

@@ -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.

+ 1 - 0
src/Composer/Autoload/ClassLoader.php

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

+ 9 - 0
src/Composer/Command/Command.php

@@ -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
      */

+ 8 - 2
src/Composer/Command/CreateProjectCommand.php

@@ -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 {

+ 1 - 1
src/Composer/Command/DiagnoseCommand.php

@@ -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;

+ 2 - 1
src/Composer/Command/InitCommand.php

@@ -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)

+ 50 - 3
src/Composer/Command/LicensesCommand.php

@@ -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;
+    }
 }

+ 23 - 9
src/Composer/Command/RequireCommand.php

@@ -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)) {

+ 2 - 1
src/Composer/Command/SelfUpdateCommand.php

@@ -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>');
 

+ 4 - 0
src/Composer/Command/UpdateCommand.php

@@ -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')) {

+ 11 - 1
src/Composer/Compiler.php

@@ -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;

+ 1 - 1
src/Composer/Config.php

@@ -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)

+ 21 - 2
src/Composer/Console/Application.php

@@ -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
      */

+ 5 - 2
src/Composer/DependencyResolver/DefaultPolicy.php

@@ -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, '==')) {

+ 1 - 1
src/Composer/DependencyResolver/RuleSetGenerator.php

@@ -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));
                 }

+ 35 - 0
src/Composer/EventDispatcher/EventDispatcher.php

@@ -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
      *

+ 38 - 4
src/Composer/Installer.php

@@ -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.
      *

+ 9 - 2
src/Composer/Json/JsonManipulator.php

@@ -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;

+ 3 - 2
src/Composer/Package/Archiver/ArchiveManager.php

@@ -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;
     }

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

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

+ 1 - 1
src/Composer/Package/CompletePackage.php

@@ -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;
     }
 }

+ 12 - 1
src/Composer/Package/Locker.php

@@ -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) {

+ 73 - 9
src/Composer/Package/Version/VersionParser.php

@@ -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';
                     }
                 }

+ 14 - 2
src/Composer/Package/Version/VersionSelector.php

@@ -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()

+ 17 - 17
src/Composer/Plugin/PluginManager.php

@@ -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);
     }
 }

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

@@ -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';

+ 1 - 1
src/Composer/Repository/Vcs/GitBitbucketDriver.php

@@ -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'];

+ 2 - 2
src/Composer/Repository/Vcs/GitDriver.php

@@ -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);

+ 1 - 1
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -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'];

+ 1 - 1
src/Composer/Repository/Vcs/HgBitbucketDriver.php

@@ -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'];

+ 2 - 2
src/Composer/Repository/Vcs/HgDriver.php

@@ -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();

+ 1 - 1
src/Composer/Repository/Vcs/SvnDriver.php

@@ -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)) {

+ 8 - 4
src/Composer/Util/RemoteFilesystem.php

@@ -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
             )
         );
 

+ 47 - 36
src/Composer/Util/StreamContextFactory.php

@@ -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}");
                 }
             }
         }

+ 1 - 1
tests/Composer/Test/AllFunctionalTest.php

@@ -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));

+ 4 - 0
tests/Composer/Test/CacheTest.php

@@ -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);
 

+ 16 - 0
tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php

@@ -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);
+    }
 }

+ 32 - 1
tests/Composer/Test/EventDispatcher/EventDispatcherTest.php

@@ -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;

+ 1 - 0
tests/Composer/Test/Fixtures/installer/install-dev-using-dist.test

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

+ 2 - 1
tests/Composer/Test/Fixtures/installer/install-from-empty-lock.test

@@ -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

+ 44 - 0
tests/Composer/Test/Fixtures/installer/install-from-lock-removes-package.test

@@ -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)

+ 2 - 1
tests/Composer/Test/Fixtures/installer/install-missing-alias-from-lock.test

@@ -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

+ 2 - 0
tests/Composer/Test/Fixtures/installer/partial-update-downgrades-non-whitelisted-unstable.test

@@ -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": []
 }

+ 2 - 0
tests/Composer/Test/Fixtures/installer/partial-update-from-lock.test

@@ -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": []
 }

+ 1 - 0
tests/Composer/Test/Fixtures/installer/partial-update-without-lock.test

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

+ 3 - 1
tests/Composer/Test/Fixtures/installer/update-alias-lock.test

@@ -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": []
 }

+ 40 - 0
tests/Composer/Test/Fixtures/installer/update-prefer-lowest-stable.test

@@ -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)

+ 2 - 1
tests/Composer/Test/Fixtures/installer/update-whitelist-reads-lock.test

@@ -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--
 [

+ 32 - 0
tests/Composer/Test/Fixtures/installer/update-whitelist-removes-unused.test

@@ -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)

+ 2 - 1
tests/Composer/Test/Fixtures/installer/updating-dev-from-lock-removes-old-deps.test

@@ -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--
 [

+ 3 - 1
tests/Composer/Test/Fixtures/installer/updating-dev-updates-url-and-reference.test

@@ -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": []
 }

+ 3 - 1
tests/Composer/Test/InstallerTest.php

@@ -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);

+ 54 - 0
tests/Composer/Test/Json/JsonManipulatorTest.php

@@ -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
      */

+ 2 - 1
tests/Composer/Test/Mock/FactoryMock.php

@@ -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)
     {
     }
 }

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

@@ -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()

+ 122 - 6
tests/Composer/Test/Package/Version/VersionParserTest.php

@@ -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 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'));
     }
 
-    public function testParseConstraintsMultiDisjunctiveHasPrioOverConjuctive()
+    /**
+     * @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'),
         );
     }
 

+ 4 - 1
tests/Composer/Test/Package/Version/VersionSelectorTest.php

@@ -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'),

+ 1 - 1
tests/Composer/Test/Plugin/PluginInstallerTest.php

@@ -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(

+ 52 - 9
tests/Composer/Test/Util/StreamContextFactoryTest.php

@@ -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);
     }
 
     /**