Преглед на файлове

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

Conflicts:
	src/Composer/Factory.php
Nicolas Toniazzi преди 10 години
родител
ревизия
ad9c3d3b30
променени са 65 файла, в които са добавени 1060 реда и са изтрити 236 реда
  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)
 ### 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
   * 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",
             "name": "symfony/console",
-            "version": "v2.6.0",
+            "version": "v2.6.1",
             "target-dir": "Symfony/Component/Console",
             "target-dir": "Symfony/Component/Console",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/Console.git",
                 "url": "https://github.com/symfony/Console.git",
-                "reference": "d3bac228fd7a2aac9193e241b239880b3ba39a10"
+                "reference": "ef825fd9f809d275926547c9e57cbf14968793e8"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -173,21 +173,21 @@
             ],
             ],
             "description": "Symfony Console Component",
             "description": "Symfony Console Component",
             "homepage": "http://symfony.com",
             "homepage": "http://symfony.com",
-            "time": "2014-11-20 13:24:23"
+            "time": "2014-12-02 20:19:20"
         },
         },
         {
         {
             "name": "symfony/finder",
             "name": "symfony/finder",
-            "version": "v2.6.0",
+            "version": "v2.6.1",
             "target-dir": "Symfony/Component/Finder",
             "target-dir": "Symfony/Component/Finder",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/Finder.git",
                 "url": "https://github.com/symfony/Finder.git",
-                "reference": "d574347c652a14cfee0349f744c7880e1d9029fd"
+                "reference": "0d3ef7f6ec55a7af5eca7914eaa0dacc04ccc721"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -220,21 +220,21 @@
             ],
             ],
             "description": "Symfony Finder Component",
             "description": "Symfony Finder Component",
             "homepage": "http://symfony.com",
             "homepage": "http://symfony.com",
-            "time": "2014-11-28 10:00:40"
+            "time": "2014-12-02 20:19:20"
         },
         },
         {
         {
             "name": "symfony/process",
             "name": "symfony/process",
-            "version": "v2.6.0",
+            "version": "v2.6.1",
             "target-dir": "Symfony/Component/Process",
             "target-dir": "Symfony/Component/Process",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/Process.git",
                 "url": "https://github.com/symfony/Process.git",
-                "reference": "dc88f75d1c07791e5733f90be747961dce26cf05"
+                "reference": "bf0c9bd625f13b0b0bbe39919225cf145dfb935a"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -267,7 +267,7 @@
             ],
             ],
             "description": "Symfony Process Component",
             "description": "Symfony Process Component",
             "homepage": "http://symfony.com",
             "homepage": "http://symfony.com",
-            "time": "2014-11-04 14:29:39"
+            "time": "2014-12-02 20:19:20"
         }
         }
     ],
     ],
     "packages-dev": [
     "packages-dev": [
@@ -327,16 +327,16 @@
         },
         },
         {
         {
             "name": "phpunit/php-code-coverage",
             "name": "phpunit/php-code-coverage",
-            "version": "2.0.12",
+            "version": "2.0.13",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "7ce9da20f96964bb7a4033f53834df13328dbeab"
+                "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -388,7 +388,7 @@
                 "testing",
                 "testing",
                 "xunit"
                 "xunit"
             ],
             ],
-            "time": "2014-12-02 13:17:01"
+            "time": "2014-12-03 06:41:44"
         },
         },
         {
         {
             "name": "phpunit/php-file-iterator",
             "name": "phpunit/php-file-iterator",
@@ -574,16 +574,16 @@
         },
         },
         {
         {
             "name": "phpunit/phpunit",
             "name": "phpunit/phpunit",
-            "version": "4.3.5",
+            "version": "4.4.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "2dab9d593997db4abcf58d0daf798eb4e9cecfe1"
+                "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -600,8 +600,9 @@
                 "phpunit/phpunit-mock-objects": "~2.3",
                 "phpunit/phpunit-mock-objects": "~2.3",
                 "sebastian/comparator": "~1.0",
                 "sebastian/comparator": "~1.0",
                 "sebastian/diff": "~1.1",
                 "sebastian/diff": "~1.1",
-                "sebastian/environment": "~1.0",
+                "sebastian/environment": "~1.1",
                 "sebastian/exporter": "~1.0",
                 "sebastian/exporter": "~1.0",
+                "sebastian/global-state": "~1.0",
                 "sebastian/version": "~1.0",
                 "sebastian/version": "~1.0",
                 "symfony/yaml": "~2.0"
                 "symfony/yaml": "~2.0"
             },
             },
@@ -614,7 +615,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "4.3.x-dev"
+                    "dev-master": "4.4.x-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -623,10 +624,6 @@
                 ]
                 ]
             },
             },
             "notification-url": "https://packagist.org/downloads/",
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                "",
-                "../../symfony/yaml/"
-            ],
             "license": [
             "license": [
                 "BSD-3-Clause"
                 "BSD-3-Clause"
             ],
             ],
@@ -638,13 +635,13 @@
                 }
                 }
             ],
             ],
             "description": "The PHP Unit Testing framework.",
             "description": "The PHP Unit Testing framework.",
-            "homepage": "http://www.phpunit.de/",
+            "homepage": "https://phpunit.de/",
             "keywords": [
             "keywords": [
                 "phpunit",
                 "phpunit",
                 "testing",
                 "testing",
                 "xunit"
                 "xunit"
             ],
             ],
-            "time": "2014-11-11 10:11:09"
+            "time": "2014-12-05 06:49:03"
         },
         },
         {
         {
             "name": "phpunit/phpunit-mock-objects",
             "name": "phpunit/phpunit-mock-objects",
@@ -703,16 +700,16 @@
         },
         },
         {
         {
             "name": "sebastian/comparator",
             "name": "sebastian/comparator",
-            "version": "1.0.1",
+            "version": "1.1.0",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
                 "url": "https://github.com/sebastianbergmann/comparator.git",
-                "reference": "e54a01c0da1b87db3c5a3c4c5277ddf331da4aef"
+                "reference": "c484a80f97573ab934e37826dba0135a3301b26a"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -726,7 +723,7 @@
             "type": "library",
             "type": "library",
             "extra": {
             "extra": {
                 "branch-alias": {
                 "branch-alias": {
-                    "dev-master": "1.0.x-dev"
+                    "dev-master": "1.1.x-dev"
                 }
                 }
             },
             },
             "autoload": {
             "autoload": {
@@ -763,7 +760,7 @@
                 "compare",
                 "compare",
                 "equality"
                 "equality"
             ],
             ],
-            "time": "2014-05-11 23:00:21"
+            "time": "2014-11-16 21:32:38"
         },
         },
         {
         {
             "name": "sebastian/diff",
             "name": "sebastian/diff",
@@ -932,6 +929,57 @@
             ],
             ],
             "time": "2014-09-10 00:51:36"
             "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",
             "name": "sebastian/version",
             "version": "1.0.3",
             "version": "1.0.3",
@@ -969,17 +1017,17 @@
         },
         },
         {
         {
             "name": "symfony/yaml",
             "name": "symfony/yaml",
-            "version": "v2.6.0",
+            "version": "v2.6.1",
             "target-dir": "Symfony/Component/Yaml",
             "target-dir": "Symfony/Component/Yaml",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/symfony/Yaml.git",
                 "url": "https://github.com/symfony/Yaml.git",
-                "reference": "51c845cf3e4bfc182d1d5c05ed1c7338361d86f8"
+                "reference": "3346fc090a3eb6b53d408db2903b241af51dcb20"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "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": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -1012,7 +1060,7 @@
             ],
             ],
             "description": "Symfony Yaml Component",
             "description": "Symfony Yaml Component",
             "homepage": "http://symfony.com",
             "homepage": "http://symfony.com",
-            "time": "2014-11-20 13:24:23"
+            "time": "2014-12-02 20:19:20"
         }
         }
     ],
     ],
     "aliases": [],
     "aliases": [],

+ 12 - 17
doc/00-intro.md

@@ -47,7 +47,7 @@ any version beginning with `1.2`.
 ## System Requirements
 ## System Requirements
 
 
 Composer requires PHP 5.3.2+ to run. A few sensitive php settings and compile
 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.
 incompatibilities.
 
 
 To install packages from sources instead of simple zip archives, you will need
 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,
 Composer is multi-platform and we strive to make it run equally well on Windows,
 Linux and OSX.
 Linux and OSX.
 
 
-## Installation - *nix
+## Installation - Linux / Unix / OSX
 
 
 ### Downloading the Composer Executable
 ### 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
 #### 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
 ```sh
 curl -sS https://getcomposer.org/installer | php
 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
 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
 archive), which is an archive format for PHP which can be run on the command
 line, amongst other things.
 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`.
 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
 ## Installation - Windows
 
 
 ### Using the Installer
 ### 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
 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.
 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
 ### Manual Installation
 
 
 Change to a directory on your `PATH` and run the install snippet to download
 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
 # 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
 ## `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.
 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
 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
 it is mostly useful for projects respecting [semantic
 versioning](http://semver.org/). A common usage would be to mark the minimum
 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
 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
 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.
 `~` 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
 > **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`
 > like `~1.2` would not install it. As said above `~1.2` only means the `.2`
 > can change but the `1.` part is fixed.
 > 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:** Only updates the lock file hash to suppress warning about the
   lock file being out of date.
   lock file being out of date.
 * **--with-dependencies** Add also all dependencies of whitelisted packages to the whitelist.
 * **--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
 ## require
 
 
@@ -482,11 +484,20 @@ performance.
   a bit of time to run so it is currently not done by default.
   a bit of time to run so it is currently not done by default.
 * **--no-dev:** Disables autoload-dev rules.
 * **--no-dev:** Disables autoload-dev rules.
 
 
+## clear-cache
+
+Deletes all content from Composer's cache directories.
+
 ## licenses
 ## licenses
 
 
 Lists the name, version and license of every package installed. Use
 Lists the name, version and license of every package installed. Use
 `--format=json` to get machine readable output.
 `--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
 ## run-script
 
 
 To run [scripts](articles/scripts.md) manually you can use this command,
 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
 Lists packages that conflict with this version of this package. They
 will not be allowed to be installed together with your package.
 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
 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
 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
 #### 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)) {
         if (!empty($this->prefixesPsr0)) {
             return call_user_func_array('array_merge', $this->prefixesPsr0);
             return call_user_func_array('array_merge', $this->prefixesPsr0);
         }
         }
+
         return array();
         return array();
     }
     }
 
 

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

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

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

@@ -234,8 +234,14 @@ EOT
     {
     {
         if (null === $repositoryUrl) {
         if (null === $repositoryUrl) {
             $sourceRepo = new CompositeRepository(Factory::createDefaultRepositories($io, $config));
             $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')) {
         } elseif (0 === strpos($repositoryUrl, 'http')) {
             $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
             $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
         } else {
         } 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));
         $latest = trim($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/version', false));
 
 
         if (Composer::VERSION !== $latest && Composer::VERSION !== '@package_version@') {
         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;
         return true;

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

@@ -33,8 +33,9 @@ use Symfony\Component\Process\ExecutableFinder;
  */
  */
 class InitCommand extends Command
 class InitCommand extends Command
 {
 {
+    protected $repos;
+
     private $gitConfig;
     private $gitConfig;
-    private $repos;
     private $pool;
     private $pool;
 
 
     public function parseAuthorString($author)
     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\Package\Version\VersionParser;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Plugin\PluginEvents;
+use Composer\Package\PackageInterface;
+use Composer\Repository\RepositoryInterface;
 use Symfony\Component\Console\Helper\TableHelper;
 use Symfony\Component\Console\Helper\TableHelper;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Input\InputOption;
@@ -33,6 +35,7 @@ class LicensesCommand extends Command
             ->setDescription('Show information about licenses of dependencies')
             ->setDescription('Show information about licenses of dependencies')
             ->setDefinition(array(
             ->setDefinition(array(
                 new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
                 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
             ->setHelp(<<<EOT
 The license command displays detailed information about the licenses of
 The license command displays detailed information about the licenses of
@@ -55,9 +58,10 @@ EOT
 
 
         $versionParser = new VersionParser;
         $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);
         ksort($packages);
@@ -102,4 +106,47 @@ EOT
                 throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
                 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\Package\Version\VersionParser;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
 use Composer\Plugin\PluginEvents;
+use Composer\Repository\CompositeRepository;
+use Composer\Repository\PlatformRepository;
 
 
 /**
 /**
  * @author Jérémy Romey <jeremy@free-agent.fr>
  * @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-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('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('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
             ->setHelp(<<<EOT
 The require command adds required packages to your composer.json and installs them
 The require command adds required packages to your composer.json and installs them
@@ -78,14 +81,22 @@ EOT
         }
         }
 
 
         $json = new JsonFile($file);
         $json = new JsonFile($file);
-        $composer = $json->read();
+        $composerDefinition = $json->read();
         $composerBackup = file_get_contents($json->getPath());
         $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'));
         $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'));
 
 
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
         $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';
         $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);
         $requirements = $this->formatRequirements($requirements);
 
 
         // validate requirements format
         // validate requirements format
@@ -94,17 +105,19 @@ EOT
             $versionParser->parseConstraints($constraint);
             $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) {
             foreach ($requirements as $package => $version) {
                 $baseRequirements[$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>');
         $output->writeln('<info>'.$file.' has been '.($newlyCreated ? 'created' : 'updated').'</info>');
@@ -115,6 +128,7 @@ EOT
         $updateDevMode = !$input->getOption('update-no-dev');
         $updateDevMode = !$input->getOption('update-no-dev');
 
 
         // Update packages
         // Update packages
+        $this->resetComposer();
         $composer = $this->getComposer();
         $composer = $this->getComposer();
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $io = $this->getIO();
         $io = $this->getIO();
@@ -149,14 +163,14 @@ EOT
         return $status;
         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());
         $contents = file_get_contents($json->getPath());
 
 
         $manipulator = new JsonManipulator($contents);
         $manipulator = new JsonManipulator($contents);
 
 
         foreach ($new as $package => $constraint) {
         foreach ($new as $package => $constraint) {
-            if (!$manipulator->addLink($requireKey, $package, $constraint)) {
+            if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
                 return false;
                 return false;
             }
             }
             if (!$manipulator->removeSubNode($removeKey, $package)) {
             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('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 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 InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
+                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
             ))
             ))
             ->setHelp(<<<EOT
             ->setHelp(<<<EOT
 The <info>self-update</info> command checks getcomposer.org for newer
 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));
         $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");
         $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)) {
         if (!file_exists($tempFilename)) {
             $output->writeln('<error>The download of the new composer version failed for an unexpected reason</error>');
             $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('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('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('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
             ->setHelp(<<<EOT
 The <info>update</info> command reads the composer.json file from the
 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'))
             ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $input->getArgument('packages'))
             ->setWhitelistDependencies($input->getOption('with-dependencies'))
             ->setWhitelistDependencies($input->getOption('with-dependencies'))
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
+            ->setPreferStable($input->getOption('prefer-stable'))
+            ->setPreferLowest($input->getOption('prefer-lowest'))
         ;
         ;
 
 
         if ($input->getOption('no-plugins')) {
         if ($input->getOption('no-plugins')) {

+ 11 - 1
src/Composer/Compiler.php

@@ -55,7 +55,7 @@ class Compiler
         $date->setTimezone(new \DateTimeZone('UTC'));
         $date->setTimezone(new \DateTimeZone('UTC'));
         $this->versionDate = $date->format('Y-m-d H:i:s');
         $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) {
         if ($process->run() == 0) {
             $this->version = trim($process->getOutput());
             $this->version = trim($process->getOutput());
         } else {
         } else {
@@ -212,6 +212,16 @@ class Compiler
  * the license that is located at the bottom of this file.
  * 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');
 Phar::mapPhar('composer.phar');
 
 
 EOF;
 EOF;

+ 1 - 1
src/Composer/Config.php

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

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

@@ -99,7 +99,8 @@ class Application extends BaseApplication
             if ($name = $this->getCommandName($input)) {
             if ($name = $this->getCommandName($input)) {
                 try {
                 try {
                     $commandName = $this->find($name)->getName();
                     $commandName = $this->find($name)->getName();
-                } catch (\InvalidArgumentException $e) {}
+                } catch (\InvalidArgumentException $e) {
+                }
             }
             }
             if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
             if ($commandName !== 'self-update' && $commandName !== 'selfupdate') {
                 if (time() > COMPOSER_DEV_WARNING_TIME) {
                 if (time() > COMPOSER_DEV_WARNING_TIME) {
@@ -176,7 +177,7 @@ class Application extends BaseApplication
     public function renderException($exception, $output)
     public function renderException($exception, $output)
     {
     {
         try {
         try {
-            $composer = $this->getComposer(false);
+            $composer = $this->getComposer(false, true);
             if ($composer) {
             if ($composer) {
                 $config = $composer->getConfig();
                 $config = $composer->getConfig();
 
 
@@ -190,6 +191,16 @@ class Application extends BaseApplication
         } catch (\Exception $e) {
         } 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);
         return parent::renderException($exception, $output);
     }
     }
 
 
@@ -219,6 +230,14 @@ class Application extends BaseApplication
         return $this->composer;
         return $this->composer;
     }
     }
 
 
+    /**
+     * Removes the cached composer instance
+     */
+    public function resetComposer()
+    {
+        $this->composer = null;
+    }
+
     /**
     /**
      * @return IOInterface
      * @return IOInterface
      */
      */

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

@@ -24,10 +24,12 @@ use Composer\Package\LinkConstraint\VersionConstraint;
 class DefaultPolicy implements PolicyInterface
 class DefaultPolicy implements PolicyInterface
 {
 {
     private $preferStable;
     private $preferStable;
+    private $preferLowest;
 
 
-    public function __construct($preferStable = false)
+    public function __construct($preferStable = false, $preferLowest = false)
     {
     {
         $this->preferStable = $preferStable;
         $this->preferStable = $preferStable;
+        $this->preferLowest = $preferLowest;
     }
     }
 
 
     public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
     public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
@@ -195,6 +197,7 @@ class DefaultPolicy implements PolicyInterface
 
 
     protected function pruneToBestVersion(Pool $pool, $literals)
     protected function pruneToBestVersion(Pool $pool, $literals)
     {
     {
+        $operator = $this->preferLowest ? '<' : '>';
         $bestLiterals = array($literals[0]);
         $bestLiterals = array($literals[0]);
         $bestPackage = $pool->literalToPackage($literals[0]);
         $bestPackage = $pool->literalToPackage($literals[0]);
         foreach ($literals as $i => $literal) {
         foreach ($literals as $i => $literal) {
@@ -204,7 +207,7 @@ class DefaultPolicy implements PolicyInterface
 
 
             $package = $pool->literalToPackage($literal);
             $package = $pool->literalToPackage($literal);
 
 
-            if ($this->versionCompare($package, $bestPackage, '>')) {
+            if ($this->versionCompare($package, $bestPackage, $operator)) {
                 $bestPackage = $package;
                 $bestPackage = $package;
                 $bestLiterals = array($literal);
                 $bestLiterals = array($literal);
             } elseif ($this->versionCompare($package, $bestPackage, '==')) {
             } 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) {
                 if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                     $this->addRule(RuleSet::TYPE_PACKAGE, $rule = $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
                     $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;
                     $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));
                     $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;
         $return = 0;
         foreach ($listeners as $callable) {
         foreach ($listeners as $callable) {
             if (!is_string($callable) && is_callable($callable)) {
             if (!is_string($callable) && is_callable($callable)) {
+                $event = $this->checkListenerExpectedEvent($callable, $event);
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
             } elseif ($this->isPhpScript($callable)) {
             } elseif ($this->isPhpScript($callable)) {
                 $className = substr($callable, 0, strpos($callable, '::'));
                 $className = substr($callable, 0, strpos($callable, '::'));
@@ -200,9 +201,43 @@ class EventDispatcher
      */
      */
     protected function executeEventPhpScript($className, $methodName, Event $event)
     protected function executeEventPhpScript($className, $methodName, Event $event)
     {
     {
+        $event = $this->checkListenerExpectedEvent(array($className, $methodName), $event);
+
         return $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
      * Add a listener for a particular event
      *
      *

+ 38 - 4
src/Composer/Installer.php

@@ -107,6 +107,8 @@ class Installer
     protected $update = false;
     protected $update = false;
     protected $runScripts = true;
     protected $runScripts = true;
     protected $ignorePlatformReqs = false;
     protected $ignorePlatformReqs = false;
+    protected $preferStable = false;
+    protected $preferLowest = false;
     /**
     /**
      * Array of package names/globs flagged for update
      * Array of package names/globs flagged for update
      *
      *
@@ -307,7 +309,8 @@ class Installer
                     $aliases,
                     $aliases,
                     $this->package->getMinimumStability(),
                     $this->package->getMinimumStability(),
                     $this->package->getStabilityFlags(),
                     $this->package->getStabilityFlags(),
-                    $this->package->getPreferStable()
+                    $this->preferStable || $this->package->getPreferStable(),
+                    $this->preferLowest
                 );
                 );
                 if ($updatedLock) {
                 if ($updatedLock) {
                     $this->io->write('<info>Writing lock file</info>');
                     $this->io->write('<info>Writing lock file</info>');
@@ -694,16 +697,21 @@ class Installer
     private function createPolicy()
     private function createPolicy()
     {
     {
         $preferStable = null;
         $preferStable = null;
+        $preferLowest = null;
         if (!$this->update && $this->locker->isLocked()) {
         if (!$this->update && $this->locker->isLocked()) {
             $preferStable = $this->locker->getPreferStable();
             $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
         // so in this case we use the composer.json info
         if (null === $preferStable) {
         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)
     private function createRequest(Pool $pool, RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
@@ -1244,6 +1252,32 @@ class Installer
         return $this;
         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.
      * Disables plugins.
      *
      *

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

@@ -31,7 +31,7 @@ class JsonManipulator
         if (!self::$RECURSE_BLOCKS) {
         if (!self::$RECURSE_BLOCKS) {
             self::$RECURSE_BLOCKS = '(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{[^{}]*\})*\})*\})*\})*';
             self::$RECURSE_BLOCKS = '(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{(?:[^{}]*|\{[^{}]*\})*\})*\})*\})*';
             self::$RECURSE_ARRAYS = '(?:[^\]]*|\[(?:[^\]]*|\[(?:[^\]]*|\[(?:[^\]]*|\[[^\]]*\])*\])*\])*\]|'.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.'\})';
             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;
         return $this->contents . $this->newline;
     }
     }
 
 
-    public function addLink($type, $package, $constraint)
+    public function addLink($type, $package, $constraint, $sortPackages = false)
     {
     {
         $decoded = JsonFile::parseJson($this->contents);
         $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];
         $this->contents = $matches[1] . $matches[2] . $links . $matches[4];
 
 
         return true;
         return true;

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

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

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

@@ -56,7 +56,6 @@ abstract class BasePackage implements PackageInterface
     protected $repository;
     protected $repository;
     protected $transportOptions;
     protected $transportOptions;
 
 
-
     /**
     /**
      * All descendants' constructors should call this parent constructor
      * 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()
     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;
         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()
     public function getAliases()
     {
     {
         $lockData = $this->getLockData();
         $lockData = $this->getLockData();
@@ -213,10 +222,11 @@ class Locker
      * @param string $minimumStability
      * @param string $minimumStability
      * @param array  $stabilityFlags
      * @param array  $stabilityFlags
      * @param bool   $preferStable
      * @param bool   $preferStable
+     * @param bool   $preferLowest
      *
      *
      * @return bool
      * @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(
         $lock = array(
             '_readme' => array('This file locks the dependencies of your project to a known state',
             '_readme' => array('This file locks the dependencies of your project to a known state',
@@ -229,6 +239,7 @@ class Locker
             'minimum-stability' => $minimumStability,
             'minimum-stability' => $minimumStability,
             'stability-flags' => $stabilityFlags,
             'stability-flags' => $stabilityFlags,
             'prefer-stable' => $preferStable,
             'prefer-stable' => $preferStable,
+            'prefer-lowest' => $preferLowest,
         );
         );
 
 
         foreach ($aliases as $package => $versions) {
         foreach ($aliases as $package => $versions) {

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

@@ -103,6 +103,11 @@ class VersionParser
             $version = $match[1];
             $version = $match[1];
         }
         }
 
 
+        // ignore build metadata
+        if (preg_match('{^([^,\s+]+)\+[^\s]+$}', $version, $match)) {
+            $version = $match[1];
+        }
+
         // match master-like branches
         // match master-like branches
         if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
         if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
             return '9999999-dev';
             return '9999999-dev';
@@ -178,10 +183,10 @@ class VersionParser
             return $this->normalize($name);
             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 = '';
             $version = '';
             for ($i = 1; $i < 5; $i++) {
             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';
             return str_replace('x', '9999999', $version).'-dev';
@@ -230,11 +235,10 @@ class VersionParser
             $constraints = $match[1];
             $constraints = $match[1];
         }
         }
 
 
-        $orConstraints = preg_split('{\s*\|\s*}', trim($constraints));
+        $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
         $orGroups = array();
         $orGroups = array();
         foreach ($orConstraints as $constraints) {
         foreach ($orConstraints as $constraints) {
-            $andConstraints = preg_split('{\s*,\s*}', $constraints);
-
+            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $constraints);
             if (count($andConstraints) > 1) {
             if (count($andConstraints) > 1) {
                 $constraintObjects = array();
                 $constraintObjects = array();
                 foreach ($andConstraints as $constraint) {
                 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);
             return array(new EmptyConstraint);
         }
         }
 
 
+        $versionRegex = '(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex;
+
         // match tilde constraints
         // match tilde constraints
         // like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
         // 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.
         // 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
         // however, if a stability suffix is added to the constraint, then a >= match on the current version is
         // used instead
         // 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) === '~>') {
             if (substr($constraint, 0, 2) === '~>') {
                 throw new \UnexpectedValueException(
                 throw new \UnexpectedValueException(
                     'Could not parse version constraint '.$constraint.': '.
                     '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
         // 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]) {
             if (isset($matches[3]) && '' !== $matches[3]) {
                 $position = 3;
                 $position = 3;
             } elseif (isset($matches[2]) && '' !== $matches[2]) {
             } 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
         // match operators constraints
         if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
         if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
             try {
             try {
@@ -360,7 +424,7 @@ class VersionParser
                 if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
                 if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
                     $version .= '-' . $stabilityModifier;
                     $version .= '-' . $stabilityModifier;
                 } elseif ('<' === $matches[1]) {
                 } elseif ('<' === $matches[1]) {
-                    if (!preg_match('/-stable$/', strtolower($matches[2]))) {
+                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                         $version .= '-dev';
                         $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
         // attempt to transform 2.1.1 to 2.1
         // this allows you to upgrade through minor versions
         // this allows you to upgrade through minor versions
         $semanticVersionParts = explode('.', $version);
         $semanticVersionParts = explode('.', $version);
+        $op = '~';
+
         // check to see if we have a semver-looking version
         // check to see if we have a semver-looking version
         if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) {
         if (count($semanticVersionParts) == 4 && preg_match('{^0\D?}', $semanticVersionParts[3])) {
             // remove the last parts (i.e. the patch version number and any extra)
             // 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);
             $version = implode('.', $semanticVersionParts);
         } else {
         } else {
             return $prettyVersion;
             return $prettyVersion;
@@ -118,7 +130,7 @@ class VersionSelector
         }
         }
 
 
         // 2.1 -> ~2.1
         // 2.1 -> ~2.1
-        return '~'.$version;
+        return $op.$version;
     }
     }
 
 
     private function getParser()
     private function getParser()

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

@@ -28,12 +28,13 @@ use Composer\DependencyResolver\Pool;
  * Plugin manager
  * Plugin manager
  *
  *
  * @author Nils Adermann <naderman@naderman.de>
  * @author Nils Adermann <naderman@naderman.de>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
  */
  */
 class PluginManager
 class PluginManager
 {
 {
     protected $composer;
     protected $composer;
     protected $io;
     protected $io;
-    protected $globalRepository;
+    protected $globalComposer;
     protected $versionParser;
     protected $versionParser;
 
 
     protected $plugins = array();
     protected $plugins = array();
@@ -44,15 +45,15 @@ class PluginManager
     /**
     /**
      * Initializes plugin manager
      * Initializes plugin manager
      *
      *
-     * @param Composer            $composer
      * @param IOInterface         $io
      * @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->io = $io;
-        $this->globalRepository = $globalRepository;
+        $this->composer = $composer;
+        $this->globalComposer = $globalComposer;
         $this->versionParser = new VersionParser();
         $this->versionParser = new VersionParser();
     }
     }
 
 
@@ -62,12 +63,12 @@ class PluginManager
     public function loadInstalledPlugins()
     public function loadInstalledPlugins()
     {
     {
         $repo = $this->composer->getRepositoryManager()->getLocalRepository();
         $repo = $this->composer->getRepositoryManager()->getLocalRepository();
-
+        $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
         if ($repo) {
         if ($repo) {
             $this->loadRepository($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']);
         $classes = is_array($extra['class']) ? $extra['class'] : array($extra['class']);
 
 
-        $pool = new Pool('dev');
         $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
         $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
+        $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
+
+        $pool = new Pool('dev');
         $pool->addRepository($localRepo);
         $pool->addRepository($localRepo);
-        if ($this->globalRepository) {
-            $pool->addRepository($this->globalRepository);
+        if ($globalRepo) {
+            $pool->addRepository($globalRepo);
         }
         }
 
 
         $autoloadPackages = array($package->getName() => $package);
         $autoloadPackages = array($package->getName() => $package);
@@ -219,7 +222,7 @@ class PluginManager
         $generator = $this->composer->getAutoloadGenerator();
         $generator = $this->composer->getAutoloadGenerator();
         $autoloads = array();
         $autoloads = array();
         foreach ($autoloadPackages as $autoloadPackage) {
         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);
             $autoloads[] = array($autoloadPackage, $downloadPath);
         }
         }
 
 
@@ -261,9 +264,6 @@ class PluginManager
             return $this->composer->getInstallationManager()->getInstallPath($package);
             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->config = $config;
         $this->options = $repoConfig['options'];
         $this->options = $repoConfig['options'];
         $this->url = $repoConfig['url'];
         $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->io = $io;
         $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->url), 'a-z0-9.$');
         $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();
         $this->loader = new ArrayLoader();
@@ -395,7 +395,7 @@ class ComposerRepository extends ArrayRepository
 
 
         $jsonUrlParts = parse_url($this->url);
         $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;
             $jsonUrl = $this->url;
         } else {
         } else {
             $jsonUrl = $this->url . '/packages.json';
             $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);
             $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;
                 $resource = $this->getScheme() . '://api.bitbucket.org/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
                 $changeset = JsonFile::parseJson($this->getContents($resource), $resource);
                 $changeset = JsonFile::parseJson($this->getContents($resource), $resource);
                 $composer['time'] = $changeset['timestamp'];
                 $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);
             $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);
                 $this->process->execute(sprintf('git log -1 --format=%%at %s', ProcessExecutor::escape($identifier)), $output, $this->repoDir);
                 $date = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                 $date = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                 $composer['time'] = $date->format('Y-m-d H:i:s');
                 $composer['time'] = $date->format('Y-m-d H:i:s');
@@ -227,7 +227,7 @@ class GitDriver extends VcsDriver
         if (Filesystem::isLocalPath($url)) {
         if (Filesystem::isLocalPath($url)) {
             $url = Filesystem::getPlatformPath($url);
             $url = Filesystem::getPlatformPath($url);
             if (!is_dir($url)) {
             if (!is_dir($url)) {
-                throw new \RuntimeException('Directory does not exist: '.$url);
+                return false;
             }
             }
 
 
             $process = new ProcessExecutor($io);
             $process = new ProcessExecutor($io);

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

@@ -171,7 +171,7 @@ class GitHubDriver extends VcsDriver
             if ($composer) {
             if ($composer) {
                 $composer = JsonFile::parseJson($composer, $resource);
                 $composer = JsonFile::parseJson($composer, $resource);
 
 
-                if (!isset($composer['time'])) {
+                if (empty($composer['time'])) {
                     $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
                     $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
                     $commit = JsonFile::parseJson($this->getContents($resource), $resource);
                     $commit = JsonFile::parseJson($this->getContents($resource), $resource);
                     $composer['time'] = $commit['commit']['committer']['date'];
                     $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);
             $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;
                 $resource = $this->getScheme() . '://bitbucket.org/api/1.0/repositories/'.$this->owner.'/'.$this->repository.'/changesets/'.$identifier;
                 $changeset = JsonFile::parseJson($this->getContents($resource), $resource);
                 $changeset = JsonFile::parseJson($this->getContents($resource), $resource);
                 $composer['time'] = $changeset['timestamp'];
                 $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);
             $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);
                 $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'));
                 $date = new \DateTime(trim($output), new \DateTimeZone('UTC'));
                 $composer['time'] = $date->format('Y-m-d H:i:s');
                 $composer['time'] = $date->format('Y-m-d H:i:s');
@@ -200,7 +200,7 @@ class HgDriver extends VcsDriver
         if (Filesystem::isLocalPath($url)) {
         if (Filesystem::isLocalPath($url)) {
             $url = Filesystem::getPlatformPath($url);
             $url = Filesystem::getPlatformPath($url);
             if (!is_dir($url)) {
             if (!is_dir($url)) {
-                throw new \RuntimeException('Directory does not exist: '.$url);
+                return false;
             }
             }
 
 
             $process = new ProcessExecutor();
             $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);
             $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);
                 $output = $this->execute('svn info', $this->baseUrl . $path . $rev);
                 foreach ($this->process->splitLines($output) as $line) {
                 foreach ($this->process->splitLines($output) as $line) {
                     if ($line && preg_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {
                     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)
     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(
         $headers = array(
             sprintf(
             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,
                 Composer::VERSION === '@package_version@' ? 'source' : Composer::VERSION,
                 php_uname('s'),
                 php_uname('s'),
                 php_uname('r'),
                 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']);
             $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)) {
         if (!empty($proxy)) {
             $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
             $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
             $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
             $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
@@ -64,48 +77,46 @@ final class StreamContextFactory
 
 
             $options['http']['proxy'] = $proxyURL;
             $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')) {
         if (defined('HHVM_VERSION')) {
             $this->markTestSkipped('Building the phar does not work on HHVM.');
             $this->markTestSkipped('Building the phar does not work on HHVM.');
         }
         }
-        
+
         $fs = new Filesystem;
         $fs = new Filesystem;
         $fs->removeDirectory(dirname(self::$pharPath));
         $fs->removeDirectory(dirname(self::$pharPath));
         $fs->ensureDirectoryExists(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()
     public function setUp()
     {
     {
+        if (getenv('TRAVIS')) {
+            $this->markTestSkipped('Test causes intermittent failures on Travis');
+        }
+
         $this->root = sys_get_temp_dir() . '/composer_testdir';
         $this->root = sys_get_temp_dir() . '/composer_testdir';
         $this->ensureDirectoryExistsAndClear($this->root);
         $this->ensureDirectoryExistsAndClear($this->root);
 
 

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

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

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

@@ -48,6 +48,7 @@ install --prefer-dist
         "a/a": 20
         "a/a": 20
     },
     },
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "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": [],
     "aliases": [],
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --RUN--
 --RUN--
 install
 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": [],
     "aliases": [],
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": [],
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --RUN--
 --RUN--
 install
 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
         "b/unstable": 15
     },
     },
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "platform-dev": []
 }
 }
@@ -59,6 +60,7 @@ update c/uptodate
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "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
         "b/unstable": 15
     },
     },
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "platform-dev": []
 }
 }
@@ -59,6 +60,7 @@ update b/unstable
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "platform-dev": []
 }
 }

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

@@ -43,6 +43,7 @@ update b/unstable
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "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": [],
     "aliases": [],
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": [],
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --INSTALLED--
 --INSTALLED--
 [
 [
@@ -66,6 +67,7 @@ update
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": [],
     "stability-flags": [],
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "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": [],
     "aliases": [],
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --INSTALLED--
 --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": [],
     "aliases": [],
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": [],
     "stability-flags": [],
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --INSTALLED--
 --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": [],
     "aliases": [],
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": {"a/a":20},
     "stability-flags": {"a/a":20},
-    "prefer-stable": false
+    "prefer-stable": false,
+    "prefer-lowest": false
 }
 }
 --INSTALLED--
 --INSTALLED--
 [
 [
@@ -59,6 +60,7 @@ update
     "minimum-stability": "dev",
     "minimum-stability": "dev",
     "stability-flags": {"a/a":20},
     "stability-flags": {"a/a":20},
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": [],
     "platform": [],
     "platform-dev": []
     "platform-dev": []
 }
 }

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

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

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

@@ -73,6 +73,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
             ),
             ),
             array(
             array(
                 '{
                 '{
+    "empty": "",
     "require": {
     "require": {
         "foo": "bar"
         "foo": "bar"
     }
     }
@@ -81,6 +82,7 @@ class JsonManipulatorTest extends \PHPUnit_Framework_TestCase
                 'vendor/baz',
                 'vendor/baz',
                 'qux',
                 'qux',
                 '{
                 '{
+    "empty": "",
     "require": {
     "require": {
         "foo": "bar",
         "foo": "bar",
         "vendor/baz": "qux"
         "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
      * @dataProvider removeSubNodeProvider
      */
      */

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

@@ -16,6 +16,7 @@ use Composer\Config;
 use Composer\Factory;
 use Composer\Factory;
 use Composer\Repository;
 use Composer\Repository;
 use Composer\Repository\RepositoryManager;
 use Composer\Repository\RepositoryManager;
+use Composer\Repository\WritableRepositoryInterface;
 use Composer\Installer;
 use Composer\Installer;
 use Composer\IO\IOInterface;
 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' => array(),
                 'platform-dev' => array(),
                 'platform-dev' => array(),
                 'prefer-stable' => false,
                 '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()
     public function testLockBadPackages()
@@ -156,7 +157,7 @@ class LockerTest extends \PHPUnit_Framework_TestCase
 
 
         $this->setExpectedException('LogicException');
         $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()
     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'),
             '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'        => array('10.4.13-beta',        '10.4.13.0-beta'),
             'parses long/2'      => array('10.4.13beta2',        '10.4.13.0-beta2'),
             '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 shorthand'   => array('10.4.13-b',           '10.4.13.0-beta'),
             'expand shorthand2'  => array('10.4.13-b5',          '10.4.13.0-beta5'),
             'expand shorthand2'  => array('10.4.13-b5',          '10.4.13.0-beta5'),
             'strips leading v'   => array('v1.0.0',              '1.0.0.0'),
             '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 arbitrary2'  => array('DEV-FOOBAR',          'dev-FOOBAR'),
             'parses arbitrary3'  => array('dev-feature/foo',     'dev-feature/foo'),
             'parses arbitrary3'  => array('dev-feature/foo',     'dev-feature/foo'),
             'ignores aliases'    => array('dev-master as 1.0.0', '9999999-dev'),
             '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'),
             'invalid type'      => array('1.0.0-meh'),
             'too many bits'     => array('1.0.0.0.0'),
             'too many bits'     => array('1.0.0.0.0'),
             'non-dev arbitrary' => array('feature-foo'),
             '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'         => array('*',           new EmptyConstraint()),
             'match any/2'       => array('*.*',         new EmptyConstraint()),
             'match any/2'       => array('*.*',         new EmptyConstraint()),
             'match any/3'       => array('*.x.*',       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'         => array('<>1.0.0',     new VersionConstraint('<>', '1.0.0.0')),
             'not equal/2'       => 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')),
             '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')),
             'completes version' => array('=1.0',        new VersionConstraint('=', '1.0.0.0')),
             'shorthand beta'    => array('1.2.3b5',     new VersionConstraint('=', '1.2.3.0-beta5')),
             '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'    => 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'    => array('>=dev-master',    new VersionConstraint('>=', '9999999-dev')),
             'accepts master/2'  => 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')),
             '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('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.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.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('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')),
             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;
         $parser = new VersionParser;
         $first = new VersionConstraint('>', '2.0.0.0');
         $first = new VersionConstraint('>', '2.0.0.0');
         $second = new VersionConstraint('<=', '3.0.0.0');
         $second = new VersionConstraint('<=', '3.0.0.0');
         $multi = new MultiConstraint(array($first, $second));
         $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;
         $parser = new VersionParser;
         $first = new VersionConstraint('>', '2.0.0.0');
         $first = new VersionConstraint('>', '2.0.0.0');
@@ -308,7 +412,16 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
         $third = new VersionConstraint('>', '2.0.6.0');
         $third = new VersionConstraint('>', '2.0.6.0');
         $multi1 = new MultiConstraint(array($first, $second));
         $multi1 = new MultiConstraint(array($first, $second));
         $multi2 = new MultiConstraint(array($multi1, $third), false);
         $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()
     public function testParseConstraintsMultiWithStabilities()
@@ -335,6 +448,9 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
         return array(
         return array(
             'empty '            => array(''),
             'empty '            => array(''),
             'invalid version'   => array('1.0.0-meh'),
             '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('v1.2.1', false, 'stable', '~1.2'),
             array('3.1.2-pl2', false, 'stable', '~3.1'),
             array('3.1.2-pl2', false, 'stable', '~3.1'),
             array('3.1.2-patch', 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('2.0-beta.1', false, 'beta', '~2.0@beta'),
             array('3.1.2-alpha5', false, 'alpha', '~3.1@alpha'),
             array('3.1.2-alpha5', false, 'alpha', '~3.1@alpha'),
             array('3.0-RC2', false, 'RC', '~3.0@RC'),
             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->setInstallationManager($im);
         $this->composer->setAutoloadGenerator($this->autoloadGenerator);
         $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);
         $this->composer->setPluginManager($this->pm);
 
 
         $config->merge(array(
         $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['http_proxy']);
         unset($_SERVER['http_proxy']);
+        unset($_SERVER['HTTPS_PROXY']);
+        unset($_SERVER['https_proxy']);
         unset($_SERVER['no_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['http_proxy']);
         unset($_SERVER['http_proxy']);
+        unset($_SERVER['HTTPS_PROXY']);
+        unset($_SERVER['https_proxy']);
         unset($_SERVER['no_proxy']);
         unset($_SERVER['no_proxy']);
     }
     }
 
 
@@ -126,17 +130,56 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
     {
     {
         $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net';
         $_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);
         $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);
     }
     }
 
 
     /**
     /**