Parcourir la source

Merge remote-tracking branch 'parent/master'

Conflicts:
	src/Composer/Factory.php
Nicolas Toniazzi il y a 10 ans
Parent
commit
865eab602f
63 fichiers modifiés avec 776 ajouts et 293 suppressions
  1. 29 0
      CONTRIBUTING.md
  2. 0 26
      README.md
  3. 1 1
      composer.json
  4. 17 16
      composer.lock
  5. 2 0
      doc/00-intro.md
  6. 2 2
      doc/01-basic-usage.md
  7. 2 2
      doc/02-libraries.md
  8. 15 8
      doc/03-cli.md
  9. 7 3
      doc/04-schema.md
  10. 1 1
      doc/05-repositories.md
  11. 8 4
      doc/articles/aliases.md
  12. 1 1
      doc/articles/handling-private-packages-with-satis.md
  13. 2 2
      doc/articles/http-basic-authentication.md
  14. 3 2
      doc/articles/troubleshooting.md
  15. 10 6
      res/composer-schema.json
  16. 10 2
      src/Composer/Autoload/AutoloadGenerator.php
  17. 26 0
      src/Composer/Autoload/ClassLoader.php
  18. 14 0
      src/Composer/Command/Command.php
  19. 6 2
      src/Composer/Command/ConfigCommand.php
  20. 5 2
      src/Composer/Command/CreateProjectCommand.php
  21. 1 1
      src/Composer/Command/DumpAutoloadCommand.php
  22. 11 7
      src/Composer/Command/HomeCommand.php
  23. 7 1
      src/Composer/Command/InstallCommand.php
  24. 4 2
      src/Composer/Command/RequireCommand.php
  25. 1 1
      src/Composer/Command/SelfUpdateCommand.php
  26. 1 0
      src/Composer/Command/ShowCommand.php
  27. 7 1
      src/Composer/Command/UpdateCommand.php
  28. 40 12
      src/Composer/Config.php
  29. 1 0
      src/Composer/Console/Application.php
  30. 2 2
      src/Composer/Downloader/FileDownloader.php
  31. 1 0
      src/Composer/EventDispatcher/EventDispatcher.php
  32. 86 71
      src/Composer/Factory.php
  33. 13 7
      src/Composer/IO/ConsoleIO.php
  34. 26 8
      src/Composer/Installer.php
  35. 2 2
      src/Composer/Installer/PluginInstaller.php
  36. 1 2
      src/Composer/Json/JsonFile.php
  37. 2 2
      src/Composer/Json/JsonValidationException.php
  38. 1 1
      src/Composer/Package/LinkConstraint/MultiConstraint.php
  39. 9 1
      src/Composer/Package/Loader/ArrayLoader.php
  40. 1 1
      src/Composer/Package/Loader/RootPackageLoader.php
  41. 11 0
      src/Composer/Package/Loader/ValidatingArrayLoader.php
  42. 16 0
      src/Composer/Package/Version/VersionParser.php
  43. 5 2
      src/Composer/Plugin/PluginManager.php
  44. 5 2
      src/Composer/Repository/ComposerRepository.php
  45. 1 0
      src/Composer/Repository/PearRepository.php
  46. 2 2
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  47. 6 2
      src/Composer/Repository/Vcs/GitDriver.php
  48. 2 2
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  49. 3 0
      src/Composer/Util/Git.php
  50. 1 2
      src/Composer/Util/RemoteFilesystem.php
  51. 68 13
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  52. 41 0
      tests/Composer/Test/ConfigTest.php
  53. 35 5
      tests/Composer/Test/IO/ConsoleIOTest.php
  54. 89 52
      tests/Composer/Test/InstallerTest.php
  55. 2 2
      tests/Composer/Test/Mock/FactoryMock.php
  56. 44 0
      tests/Composer/Test/Package/Loader/ArrayLoaderTest.php
  57. 2 0
      tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php
  58. 29 0
      tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php
  59. 23 0
      tests/Composer/Test/Package/Version/VersionParserTest.php
  60. 4 0
      tests/Composer/Test/Package/Version/VersionSelectorTest.php
  61. 8 0
      tests/Composer/Test/Repository/ArtifactRepositoryTest.php
  62. 1 0
      tests/Composer/Test/Repository/Pear/ChannelReaderTest.php
  63. 0 7
      tests/Composer/Test/Repository/PearRepositoryTest.php

+ 29 - 0
CONTRIBUTING.md

@@ -0,0 +1,29 @@
+Contributing to Composer
+========================
+
+Installation from Source
+------------------------
+
+Prior to contributing to Composer, you must use be able to run the tests.
+To achieve this, you must use the sources and not the phar file.
+
+1. Run `git clone https://github.com/composer/composer.git`
+2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
+3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
+
+You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
+
+Contributing policy
+-------------------
+
+All code contributions - including those of people having commit access -
+must go through a pull request and approved by a core developer before being
+merged. This is to ensure proper review of all the code.
+
+Fork the project, create a feature branch, and send us a pull request.
+
+To ensure a consistent code base, you should make sure the code follows
+the [Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html)
+which we borrowed from Symfony.
+
+If you would like to help, take a look at the [list of issues](http://github.com/composer/composer/issues).

+ 0 - 26
README.md

@@ -32,18 +32,6 @@ themselves. To create libraries/packages please read the
 3. Run Composer: `php composer.phar install`
 3. Run Composer: `php composer.phar install`
 4. Browse for more packages on [Packagist](https://packagist.org).
 4. Browse for more packages on [Packagist](https://packagist.org).
 
 
-Installation from Source
-------------------------
-
-To run tests, or develop Composer itself, you must use the sources and not the phar
-file as described above.
-
-1. Run `git clone https://github.com/composer/composer.git`
-2. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable
-3. Run Composer to get the dependencies: `cd composer && php ../composer.phar install`
-
-You can now run Composer by executing the `bin/composer` script: `php /path/to/composer/bin/composer`
-
 Global installation of Composer (manual)
 Global installation of Composer (manual)
 ----------------------------------------
 ----------------------------------------
 
 
@@ -55,20 +43,6 @@ Updating Composer
 Running `php composer.phar self-update` or equivalent will update a phar
 Running `php composer.phar self-update` or equivalent will update a phar
 install with the latest version.
 install with the latest version.
 
 
-Contributing
-------------
-
-All code contributions - including those of people having commit access -
-must go through a pull request and approved by a core developer before being
-merged. This is to ensure proper review of all the code.
-
-Fork the project, create a feature branch, and send us a pull request.
-
-To ensure a consistent code base, you should make sure the code follows
-the [Coding Standards](http://symfony.com/doc/current/contributing/code/standards.html)
-which we borrowed from Symfony.
-
-If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues).
 
 
 Community
 Community
 ---------
 ---------

+ 1 - 1
composer.json

@@ -23,7 +23,7 @@
     },
     },
     "require": {
     "require": {
         "php": ">=5.3.2",
         "php": ">=5.3.2",
-        "justinrainbow/json-schema": "~1.1",
+        "justinrainbow/json-schema": "~1.3",
         "seld/jsonlint": "~1.0",
         "seld/jsonlint": "~1.0",
         "symfony/console": "~2.3",
         "symfony/console": "~2.3",
         "symfony/finder": "~2.2",
         "symfony/finder": "~2.2",

+ 17 - 16
composer.lock

@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
         "This file is @generated automatically"
     ],
     ],
-    "hash": "0430383b5ba00e406ce4a44253f49fe7",
+    "hash": "2bc9cc8aa706b68d611d7058e4eb8de7",
     "packages": [
     "packages": [
         {
         {
             "name": "justinrainbow/json-schema",
             "name": "justinrainbow/json-schema",
@@ -327,16 +327,16 @@
         },
         },
         {
         {
             "name": "phpunit/php-code-coverage",
             "name": "phpunit/php-code-coverage",
-            "version": "2.0.13",
+            "version": "2.0.14",
             "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": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5"
+                "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
-                "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca158276c1200cc27f5409a5e338486bc0b4fc94",
+                "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -388,7 +388,7 @@
                 "testing",
                 "testing",
                 "xunit"
                 "xunit"
             ],
             ],
-            "time": "2014-12-03 06:41:44"
+            "time": "2014-12-26 13:28:33"
         },
         },
         {
         {
             "name": "phpunit/php-file-iterator",
             "name": "phpunit/php-file-iterator",
@@ -574,16 +574,16 @@
         },
         },
         {
         {
             "name": "phpunit/phpunit",
             "name": "phpunit/phpunit",
-            "version": "4.4.0",
+            "version": "4.4.1",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0"
+                "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0",
-                "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6a5e49a86ce5e33b8d0657abe145057fc513543a",
+                "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "require": {
             "require": {
@@ -641,7 +641,7 @@
                 "testing",
                 "testing",
                 "xunit"
                 "xunit"
             ],
             ],
-            "time": "2014-12-05 06:49:03"
+            "time": "2014-12-28 07:57:05"
         },
         },
         {
         {
             "name": "phpunit/phpunit-mock-objects",
             "name": "phpunit/phpunit-mock-objects",
@@ -982,16 +982,16 @@
         },
         },
         {
         {
             "name": "sebastian/version",
             "name": "sebastian/version",
-            "version": "1.0.3",
+            "version": "1.0.4",
             "source": {
             "source": {
                 "type": "git",
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/version.git",
                 "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43"
+                "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b"
             },
             },
             "dist": {
             "dist": {
                 "type": "zip",
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
-                "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/a77d9123f8e809db3fbdea15038c27a95da4058b",
+                "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b",
                 "shasum": ""
                 "shasum": ""
             },
             },
             "type": "library",
             "type": "library",
@@ -1013,7 +1013,7 @@
             ],
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "homepage": "https://github.com/sebastianbergmann/version",
             "homepage": "https://github.com/sebastianbergmann/version",
-            "time": "2014-03-07 15:35:33"
+            "time": "2014-12-15 14:25:24"
         },
         },
         {
         {
             "name": "symfony/yaml",
             "name": "symfony/yaml",
@@ -1067,6 +1067,7 @@
     "minimum-stability": "stable",
     "minimum-stability": "stable",
     "stability-flags": [],
     "stability-flags": [],
     "prefer-stable": false,
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": {
     "platform": {
         "php": ">=5.3.2"
         "php": ">=5.3.2"
     },
     },

+ 2 - 0
doc/00-intro.md

@@ -107,6 +107,8 @@ mv composer.phar /usr/local/bin/composer
 > **Note:** If the above fails due to permissions, run the `mv` line
 > **Note:** If the above fails due to permissions, run the `mv` line
 > again with sudo.
 > again with sudo.
 
 
+> **Note:** In OSX Yosemite the `/usr` directory does not exist by default. If you receive the error "/usr/local/bin/composer: No such file or directory" then you must create `/usr/local/bin/` manually before proceeding.
+
 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`.
 
 
 ## Installation - Windows
 ## Installation - Windows

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

@@ -2,7 +2,7 @@
 
 
 ## Installing
 ## Installing
 
 
-If you have not yet installed Composer, refer to to the [Intro](00-intro.md) chapter.
+If you have not yet installed Composer, refer to the [Intro](00-intro.md) chapter.
 
 
 ## `composer.json`: Project Setup
 ## `composer.json`: Project Setup
 
 
@@ -213,7 +213,7 @@ You define a mapping from namespaces to directories. The `src` directory would
 be in your project root, on the same level as `vendor` directory is. An example
 be in your project root, on the same level as `vendor` directory is. An example
 filename would be `src/Foo.php` containing an `Acme\Foo` class.
 filename would be `src/Foo.php` containing an `Acme\Foo` class.
 
 
-After adding the `autoload` field, you have to re-run `install` to re-generate
+After adding the `autoload` field, you have to re-run `dump-autoload` to re-generate
 the `vendor/autoload.php` file.
 the `vendor/autoload.php` file.
 
 
 Including that file will also return the autoloader instance, so you can store
 Including that file will also return the autoloader instance, so you can store

+ 2 - 2
doc/02-libraries.md

@@ -77,8 +77,8 @@ you can just add a `version` field:
 
 
 For every tag that looks like a version, a package version of that tag will be
 For every tag that looks like a version, a package version of that tag will be
 created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix
 created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix
-of `-patch`, `-alpha`, `-beta` or `-RC`. The suffixes can also be followed by
-a number.
+of `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`. The suffixes
+can also be followed by a number.
 
 
 Here are a few examples of valid tag names:
 Here are a few examples of valid tag names:
 
 

+ 15 - 8
doc/03-cli.md

@@ -87,7 +87,8 @@ resolution.
   installing a package, you can use `--dry-run`. This will simulate the
   installing a package, you can use `--dry-run`. This will simulate the
   installation and show you what would happen.
   installation and show you what would happen.
 * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
 * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
-* **--no-dev:** Skip installing packages listed in `require-dev`.
+* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
+* **--no-autoloader:** Skips autoloader generation.
 * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
 * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
 * **--no-plugins:** Disables plugins.
 * **--no-plugins:** Disables plugins.
 * **--no-progress:** Removes the progress display that can mess with some
 * **--no-progress:** Removes the progress display that can mess with some
@@ -129,7 +130,8 @@ php composer.phar update vendor/*
   fulfill these.
   fulfill these.
 * **--dry-run:** Simulate the command without actually doing anything.
 * **--dry-run:** Simulate the command without actually doing anything.
 * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
 * **--dev:** Install packages listed in `require-dev` (this is the default behavior).
-* **--no-dev:** Skip installing packages listed in `require-dev`.
+* **--no-dev:** Skip installing packages listed in `require-dev`. The autoloader generation skips the `autoload-dev` rules.
+* **--no-autoloader:** Skips autoloader generation.
 * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
 * **--no-scripts:** Skips execution of scripts defined in `composer.json`.
 * **--no-plugins:** Disables plugins.
 * **--no-plugins:** Disables plugins.
 * **--no-progress:** Removes the progress display that can mess with some
 * **--no-progress:** Removes the progress display that can mess with some
@@ -397,16 +399,18 @@ options.
 ### Options
 ### Options
 
 
 * **--global (-g):** Operate on the global config file located at
 * **--global (-g):** Operate on the global config file located at
-`$COMPOSER_HOME/config.json` by default.  Without this option, this command
-affects the local composer.json file or a file specified by `--file`.
+  `$COMPOSER_HOME/config.json` by default.  Without this option, this command
+  affects the local composer.json file or a file specified by `--file`.
 * **--editor (-e):** Open the local composer.json file using in a text editor as
 * **--editor (-e):** Open the local composer.json file using in a text editor as
-defined by the `EDITOR` env variable.  With the `--global` option, this opens
-the global config file.
+  defined by the `EDITOR` env variable.  With the `--global` option, this opens
+  the global config file.
 * **--unset:** Remove the configuration element named by `setting-key`.
 * **--unset:** Remove the configuration element named by `setting-key`.
 * **--list (-l):** Show the list of current config variables.  With the `--global`
 * **--list (-l):** Show the list of current config variables.  With the `--global`
- option this lists the global configuration only.
+  option this lists the global configuration only.
 * **--file="..." (-f):** Operate on a specific file instead of composer.json. Note
 * **--file="..." (-f):** Operate on a specific file instead of composer.json. Note
- that this cannot be used in conjunction with the `--global` option.
+  that this cannot be used in conjunction with the `--global` option.
+* **--absolute:** Returns absolute paths when fetching *-dir config values
+  instead of relative.
 
 
 ### Modifying Repositories
 ### Modifying Repositories
 
 
@@ -463,6 +467,9 @@ By default the command checks for the packages on packagist.org.
 * **--keep-vcs:** Skip the deletion of the VCS metadata for the created
 * **--keep-vcs:** Skip the deletion of the VCS metadata for the created
   project. This is mostly useful if you run the command in non-interactive
   project. This is mostly useful if you run the command in non-interactive
   mode.
   mode.
+* **--ignore-platform-reqs:** ignore `php`, `hhvm`, `lib-*` and `ext-*`
+  requirements and force the installation even if the local machine does not
+  fulfill these.
 
 
 ## dump-autoload
 ## dump-autoload
 
 

+ 7 - 3
doc/04-schema.md

@@ -54,8 +54,8 @@ The version of the package. In most cases this is not required and should
 be omitted (see below).
 be omitted (see below).
 
 
 This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix
 This must follow the format of `X.Y.Z` or `vX.Y.Z` with an optional suffix
-of `-dev`, `-patch`, `-alpha`, `-beta` or `-RC`. The patch, alpha, beta and
-RC suffixes can also be followed by a number.
+of `-dev`, `-patch` (`-p`), `-alpha` (`-a`), `-beta` (`-b`) or `-RC`.
+The patch, alpha, beta and RC suffixes can also be followed by a number.
 
 
 Examples:
 Examples:
 
 
@@ -67,6 +67,7 @@ Examples:
 - 1.0.0-alpha3
 - 1.0.0-alpha3
 - 1.0.0-beta2
 - 1.0.0-beta2
 - 1.0.0-RC5
 - 1.0.0-RC5
+- v2.0.4-p1
 
 
 Optional if the package repository can infer the version from somewhere, such
 Optional if the package repository can infer the version from somewhere, such
 as the VCS tag name in the VCS repository. In that case it is also recommended
 as the VCS tag name in the VCS repository. In that case it is also recommended
@@ -375,7 +376,7 @@ useful for common interfaces. A package could depend on some virtual
 `logger` package, any library that implements this logger interface would
 `logger` package, any library that implements this logger interface would
 simply list it in `provide`.
 simply list it in `provide`.
 
 
-### suggest
+#### suggest
 
 
 Suggested packages that can enhance or work well with this package. These are
 Suggested packages that can enhance or work well with this package. These are
 just informational and are displayed after the package is installed, to give
 just informational and are displayed after the package is installed, to give
@@ -790,6 +791,9 @@ The following options are supported:
   the generated Composer autoloader. When null a random one will be generated.
   the generated Composer autoloader. When null a random one will be generated.
 * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
 * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
   the autoloader.
   the autoloader.
+* **classmap-authoritative:** Defaults to `false`. If true, the composer
+  autoloader will not scan the filesystem for classes that are not found in
+  the class map. Implies 'optimize-autoloader'.
 * **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
 * **github-domains:** Defaults to `["github.com"]`. A list of domains to use in
   github mode. This is used for GitHub Enterprise setups.
   github mode. This is used for GitHub Enterprise setups.
 * **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth
 * **github-expose-hostname:** Defaults to `true`. If set to false, the OAuth

+ 1 - 1
doc/05-repositories.md

@@ -122,7 +122,7 @@ JSON request body:
 ```json
 ```json
 {
 {
     "downloads": [
     "downloads": [
-        {"name": "monolog/monolog", "version": "1.2.1.0"},
+        {"name": "monolog/monolog", "version": "1.2.1.0"}
     ]
     ]
 }
 }
 ```
 ```

+ 8 - 4
doc/articles/aliases.md

@@ -38,10 +38,14 @@ specifying a `branch-alias` field under `extra` in `composer.json`:
 }
 }
 ```
 ```
 
 
-The branch version must begin with `dev-` (non-comparable version), the alias
-must be a comparable dev version (i.e. start with numbers, and end with
-`.x-dev`). The `branch-alias` must be present on the branch that it references.
-For `dev-master`, you need to commit it on the `master` branch.
+If you alias a non-comparible version (such as dev-develop) `dev-` must prefix the
+branch name. You may also alias a comparible version (i.e. start with numbers,
+and end with `.x-dev`), but only as a more specific version.
+For example, 1.x-dev could be aliased as 1.2.x-dev.
+
+The alias must be a comparable dev version, and the `branch-alias` must be present on
+the branch that it references. For `dev-master`, you need to commit it on the
+`master` branch.
 
 
 As a result, anyone can now require `1.0.*` and it will happily install
 As a result, anyone can now require `1.0.*` and it will happily install
 `dev-master`.
 `dev-master`.

+ 1 - 1
doc/articles/handling-private-packages-with-satis.md

@@ -66,7 +66,7 @@ constraint if you want really specific versions.
 }
 }
 ```
 ```
 
 
-Once you did this, you just run `php bin/satis build <configuration file> <build dir>`.
+Once you've done this, you just run `php bin/satis build <configuration file> <build dir>`.
 For example `php bin/satis build config.json web/` would read the `config.json`
 For example `php bin/satis build config.json web/` would read the `config.json`
 file and build a static repository inside the `web/` directory.
 file and build a static repository inside the `web/` directory.
 
 

+ 2 - 2
doc/articles/http-basic-authentication.md

@@ -40,7 +40,7 @@ username/password pairs, for example:
 
 
 ```json
 ```json
 {
 {
-    "basic-auth": [
+    "basic-auth": {
         "repo.example1.org": {
         "repo.example1.org": {
             "username": "my-username1",
             "username": "my-username1",
             "password": "my-secret-password1"
             "password": "my-secret-password1"
@@ -49,7 +49,7 @@ username/password pairs, for example:
             "username": "my-username2",
             "username": "my-username2",
             "password": "my-secret-password2"
             "password": "my-secret-password2"
         }
         }
-    ]
+    }
 }
 }
 ```
 ```
 
 

+ 3 - 2
doc/articles/troubleshooting.md

@@ -114,8 +114,9 @@ php -d memory_limit=-1 composer.phar <...>
 ## "The system cannot find the path specified" (Windows)
 ## "The system cannot find the path specified" (Windows)
 
 
 1. Open regedit.
 1. Open regedit.
-2. Search for an ```AutoRun``` key inside ```HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor```
-   or ```HKEY_CURRENT_USER\Software\Microsoft\Command Processor```.
+2. Search for an `AutoRun` key inside `HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor`,
+   `HKEY_CURRENT_USER\Software\Microsoft\Command Processor`
+   or `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Command Processor`.
 3. Check if it contains any path to non-existent file, if it's the case, just remove them.
 3. Check if it contains any path to non-existent file, if it's the case, just remove them.
 
 
 ## API rate limit and OAuth tokens
 ## API rate limit and OAuth tokens

+ 10 - 6
res/composer-schema.json

@@ -1,12 +1,13 @@
 {
 {
+    "$schema": "http://json-schema.org/draft-04/schema#",
     "name": "Package",
     "name": "Package",
     "type": "object",
     "type": "object",
     "additionalProperties": false,
     "additionalProperties": false,
+    "required": [ "name", "description" ],
     "properties": {
     "properties": {
         "name": {
         "name": {
             "type": "string",
             "type": "string",
-            "description": "Package name, including 'vendor-name/' prefix.",
-            "required": true
+            "description": "Package name, including 'vendor-name/' prefix."
         },
         },
         "type": {
         "type": {
             "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
             "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
@@ -18,8 +19,7 @@
         },
         },
         "description": {
         "description": {
             "type": "string",
             "type": "string",
-            "description": "Short package description.",
-            "required": true
+            "description": "Short package description."
         },
         },
         "keywords": {
         "keywords": {
             "type": "array",
             "type": "array",
@@ -51,11 +51,11 @@
             "items": {
             "items": {
                 "type": "object",
                 "type": "object",
                 "additionalProperties": false,
                 "additionalProperties": false,
+                "required": [ "name"],
                 "properties": {
                 "properties": {
                     "name": {
                     "name": {
                         "type": "string",
                         "type": "string",
-                        "description": "Full name of the author.",
-                        "required": true
+                        "description": "Full name of the author."
                     },
                     },
                     "email": {
                     "email": {
                         "type": "string",
                         "type": "string",
@@ -197,6 +197,10 @@
                     "type": "boolean",
                     "type": "boolean",
                     "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
                     "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
                 },
                 },
+                "classmap-authoritative": {
+                    "type": "boolean",
+                    "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
+                },
                 "github-domains": {
                 "github-domains": {
                     "type": "array",
                     "type": "array",
                     "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
                     "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",

+ 10 - 2
src/Composer/Autoload/AutoloadGenerator.php

@@ -63,6 +63,7 @@ class AutoloadGenerator
         $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
         $vendorPath = $filesystem->normalizePath(realpath($config->get('vendor-dir')));
         $useGlobalIncludePath = (bool) $config->get('use-include-path');
         $useGlobalIncludePath = (bool) $config->get('use-include-path');
         $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
         $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
+        $classMapAuthoritative = $config->get('classmap-authoritative');
         $targetDir = $vendorPath.'/'.$targetDir;
         $targetDir = $vendorPath.'/'.$targetDir;
         $filesystem->ensureDirectoryExists($targetDir);
         $filesystem->ensureDirectoryExists($targetDir);
 
 
@@ -226,7 +227,7 @@ EOF;
             file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
             file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
         }
         }
         file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
         file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
-        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader));
+        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFile, $targetDirLoader, (bool) $includeFilesFile, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative));
 
 
         // use stream_copy_to_stream instead of copy
         // use stream_copy_to_stream instead of copy
         // to work around https://bugs.php.net/bug.php?id=64634
         // to work around https://bugs.php.net/bug.php?id=64634
@@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
 AUTOLOAD;
 AUTOLOAD;
     }
     }
 
 
-    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader)
+    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $classMapAuthoritative)
     {
     {
         // TODO the class ComposerAutoloaderInit should be revert to a closure
         // TODO the class ComposerAutoloaderInit should be revert to a closure
         // when APC has been fixed:
         // when APC has been fixed:
@@ -520,6 +521,13 @@ PSR4;
 CLASSMAP;
 CLASSMAP;
         }
         }
 
 
+        if ($classMapAuthoritative) {
+            $file .= <<<'CLASSMAPAUTHORITATIVE'
+        $loader->setClassMapAuthoritative(true);
+
+CLASSMAPAUTHORITATIVE;
+        }
+
         if ($useGlobalIncludePath) {
         if ($useGlobalIncludePath) {
             $file .= <<<'INCLUDEPATH'
             $file .= <<<'INCLUDEPATH'
         $loader->setUseIncludePath(true);
         $loader->setUseIncludePath(true);

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

@@ -54,6 +54,8 @@ class ClassLoader
     private $useIncludePath = false;
     private $useIncludePath = false;
     private $classMap = array();
     private $classMap = array();
 
 
+    private $classMapAuthoritative = false;
+
     public function getPrefixes()
     public function getPrefixes()
     {
     {
         if (!empty($this->prefixesPsr0)) {
         if (!empty($this->prefixesPsr0)) {
@@ -248,6 +250,27 @@ class ClassLoader
         return $this->useIncludePath;
         return $this->useIncludePath;
     }
     }
 
 
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
     /**
     /**
      * Registers this instance as an autoloader.
      * Registers this instance as an autoloader.
      *
      *
@@ -299,6 +322,9 @@ class ClassLoader
         if (isset($this->classMap[$class])) {
         if (isset($this->classMap[$class])) {
             return $this->classMap[$class];
             return $this->classMap[$class];
         }
         }
+        if ($this->classMapAuthoritative) {
+            return false;
+        }
 
 
         $file = $this->findFileWithExtension($class, '.php');
         $file = $this->findFileWithExtension($class, '.php');
 
 

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

@@ -16,6 +16,8 @@ use Composer\Composer;
 use Composer\Console\Application;
 use Composer\Console\Application;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\IO\NullIO;
 use Composer\IO\NullIO;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Command\Command as BaseCommand;
 use Symfony\Component\Console\Command\Command as BaseCommand;
 
 
 /**
 /**
@@ -102,4 +104,16 @@ abstract class Command extends BaseCommand
     {
     {
         $this->io = $io;
         $this->io = $io;
     }
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function initialize(InputInterface $input, OutputInterface $output)
+    {
+        if (true === $input->hasParameterOption(array('--no-ansi')) && $input->hasOption('no-progress')) {
+            $input->setOption('no-progress', true);
+        }
+
+        parent::initialize($input, $output);
+    }
 }
 }

+ 6 - 2
src/Composer/Command/ConfigCommand.php

@@ -57,6 +57,7 @@ class ConfigCommand extends Command
                 new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                 new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                 new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                 new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                 new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'),
                 new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json', 'composer.json'),
+                new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
                 new InputArgument('setting-key', null, 'Setting key'),
                 new InputArgument('setting-key', null, 'Setting key'),
                 new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
                 new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
             ))
             ))
@@ -99,6 +100,8 @@ EOT
      */
      */
     protected function initialize(InputInterface $input, OutputInterface $output)
     protected function initialize(InputInterface $input, OutputInterface $output)
     {
     {
+        parent::initialize($input, $output);
+
         if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
         if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
             throw new \RuntimeException('--file and --global can not be combined');
             throw new \RuntimeException('--file and --global can not be combined');
         }
         }
@@ -134,7 +137,7 @@ EOT
         }
         }
 
 
         if (!$this->configFile->exists()) {
         if (!$this->configFile->exists()) {
-            throw new \RuntimeException('No composer.json found in the current directory');
+            throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
         }
         }
     }
     }
 
 
@@ -218,7 +221,7 @@ EOT
 
 
                 $value = $data;
                 $value = $data;
             } elseif (isset($data['config'][$settingKey])) {
             } elseif (isset($data['config'][$settingKey])) {
-                $value = $data['config'][$settingKey];
+                $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
             } else {
             } else {
                 throw new \RuntimeException($settingKey.' is not defined');
                 throw new \RuntimeException($settingKey.' is not defined');
             }
             }
@@ -322,6 +325,7 @@ EOT
             ),
             ),
             'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
             'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
             'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
             'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
+            'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
             'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
             'prepend-autoloader' => array($booleanValidator, $booleanNormalizer),
             'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
             'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
         );
         );

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

@@ -69,6 +69,7 @@ class CreateProjectCommand extends Command
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'),
                 new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deletion vcs folder.'),
                 new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
                 new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
+                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
             ))
             ))
             ->setHelp(<<<EOT
             ->setHelp(<<<EOT
 The <info>create-project</info> command creates a new project from a given
 The <info>create-project</info> command creates a new project from a given
@@ -125,11 +126,12 @@ EOT
             $input->getOption('keep-vcs'),
             $input->getOption('keep-vcs'),
             $input->getOption('no-progress'),
             $input->getOption('no-progress'),
             $input->getOption('no-install'),
             $input->getOption('no-install'),
+            $input->getOption('ignore-platform-reqs'),
             $input
             $input
         );
         );
     }
     }
 
 
-    public function installProject(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, InputInterface $input)
+    public function installProject(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repositoryUrl = null, $disablePlugins = false, $noScripts = false, $keepVcs = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, InputInterface $input)
     {
     {
         $oldCwd = getcwd();
         $oldCwd = getcwd();
 
 
@@ -159,7 +161,8 @@ EOT
             $installer->setPreferSource($preferSource)
             $installer->setPreferSource($preferSource)
                 ->setPreferDist($preferDist)
                 ->setPreferDist($preferDist)
                 ->setDevMode($installDevPackages)
                 ->setDevMode($installDevPackages)
-                ->setRunScripts( ! $noScripts);
+                ->setRunScripts(!$noScripts)
+                ->setIgnorePlatformRequirements($ignorePlatformReqs);
 
 
             if ($disablePlugins) {
             if ($disablePlugins) {
                 $installer->disablePlugins();
                 $installer->disablePlugins();

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

@@ -52,7 +52,7 @@ EOT
         $package = $composer->getPackage();
         $package = $composer->getPackage();
         $config = $composer->getConfig();
         $config = $composer->getConfig();
 
 
-        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
 
         if ($optimize) {
         if ($optimize) {
             $output->writeln('<info>Generating optimized autoload files</info>');
             $output->writeln('<info>Generating optimized autoload files</info>');

+ 11 - 7
src/Composer/Command/HomeCommand.php

@@ -40,12 +40,14 @@ class HomeCommand extends Command
             ->setDefinition(array(
             ->setDefinition(array(
                 new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'),
                 new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'Package(s) to browse to.'),
                 new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
                 new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
+                new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
             ))
             ))
             ->setHelp(<<<EOT
             ->setHelp(<<<EOT
-The home command opens a package's repository URL or
+The home command opens or shows a package's repository URL or
 homepage in your default browser.
 homepage in your default browser.
 
 
 To open the homepage by default, use -H or --homepage.
 To open the homepage by default, use -H or --homepage.
+To show instead of open the repository or homepage URL, use -s or --show.
 EOT
 EOT
             );
             );
     }
     }
@@ -55,7 +57,7 @@ EOT
      */
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     protected function execute(InputInterface $input, OutputInterface $output)
     {
     {
-        $repo = $this->initializeRepo($input, $output);
+        $repo = $this->initializeRepo();
         $return = 0;
         $return = 0;
 
 
         foreach ($input->getArgument('packages') as $packageName) {
         foreach ($input->getArgument('packages') as $packageName) {
@@ -81,7 +83,11 @@ EOT
                 continue;
                 continue;
             }
             }
 
 
-            $this->openBrowser($url);
+            if ($input->getOption('show')) {
+                $output->writeln(sprintf('<info>%s</info>', $url));
+            } else {
+                $this->openBrowser($url);
+            }
         }
         }
 
 
         return $return;
         return $return;
@@ -138,13 +144,11 @@ EOT
     }
     }
 
 
     /**
     /**
-     * initializes the repo
+     * Initializes the repo
      *
      *
-     * @param  InputInterface      $input
-     * @param  OutputInterface     $output
      * @return CompositeRepository
      * @return CompositeRepository
      */
      */
-    private function initializeRepo(InputInterface $input, OutputInterface $output)
+    private function initializeRepo()
     {
     {
         $composer = $this->getComposer(false);
         $composer = $this->getComposer(false);
 
 

+ 7 - 1
src/Composer/Command/InstallCommand.php

@@ -41,6 +41,7 @@ class InstallCommand extends Command
                 new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                 new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                 new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
                 new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
                 new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                 new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
+                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                 new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                 new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 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.'),
@@ -74,6 +75,10 @@ EOT
             $input->setOption('no-plugins', true);
             $input->setOption('no-plugins', true);
         }
         }
 
 
+        if ($input->getOption('dev')) {
+            $output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
+        }
+
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $io = $this->getIO();
         $io = $this->getIO();
@@ -105,7 +110,7 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
             $preferDist = $input->getOption('prefer-dist');
         }
         }
 
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
 
         $install
         $install
             ->setDryRun($input->getOption('dry-run'))
             ->setDryRun($input->getOption('dry-run'))
@@ -113,6 +118,7 @@ EOT
             ->setPreferSource($preferSource)
             ->setPreferSource($preferSource)
             ->setPreferDist($preferDist)
             ->setPreferDist($preferDist)
             ->setDevMode(!$input->getOption('no-dev'))
             ->setDevMode(!$input->getOption('no-dev'))
+            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
             ->setOptimizeAutoloader($optimize)
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))

+ 4 - 2
src/Composer/Command/RequireCommand.php

@@ -38,7 +38,7 @@ class RequireCommand extends InitCommand
             ->setName('require')
             ->setName('require')
             ->setDescription('Adds required packages to your composer.json and installs them')
             ->setDescription('Adds required packages to your composer.json and installs them')
             ->setDefinition(array(
             ->setDefinition(array(
-                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
+                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Required package name optionally including a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                 new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                 new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                 new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                 new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
@@ -50,7 +50,9 @@ class RequireCommand extends InitCommand
                 new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
                 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.
+
+If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.
 
 
 If you do not want to install the new dependencies immediately you can call it with --no-update
 If you do not want to install the new dependencies immediately you can call it with --no-update
 
 

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

@@ -173,7 +173,7 @@ EOT
     protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
     protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
     {
     {
         try {
         try {
-            @chmod($newFilename, 0777 & ~umask());
+            @chmod($newFilename, fileperms($localFilename));
             if (!ini_get('phar.readonly')) {
             if (!ini_get('phar.readonly')) {
                 // test the phar validity
                 // test the phar validity
                 $phar = new \Phar($newFilename);
                 $phar = new \Phar($newFilename);

+ 1 - 0
src/Composer/Command/ShowCommand.php

@@ -41,6 +41,7 @@ class ShowCommand extends Command
     {
     {
         $this
         $this
             ->setName('show')
             ->setName('show')
+            ->setAliases(array('info'))
             ->setDescription('Show information about packages')
             ->setDescription('Show information about packages')
             ->setDefinition(array(
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'),
                 new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect'),

+ 7 - 1
src/Composer/Command/UpdateCommand.php

@@ -41,6 +41,7 @@ class UpdateCommand extends Command
                 new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
                 new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
                 new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
                 new InputOption('no-plugins', null, InputOption::VALUE_NONE, 'Disables all plugins.'),
                 new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                 new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
+                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                 new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                 new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                 new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
                 new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist.'),
@@ -78,6 +79,10 @@ EOT
             $input->setOption('no-plugins', true);
             $input->setOption('no-plugins', true);
         }
         }
 
 
+        if ($input->getOption('dev')) {
+            $output->writeln('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
+        }
+
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer = $this->getComposer(true, $input->getOption('no-plugins'));
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $io = $this->getIO();
         $io = $this->getIO();
@@ -109,7 +114,7 @@ EOT
             $preferDist = $input->getOption('prefer-dist');
             $preferDist = $input->getOption('prefer-dist');
         }
         }
 
 
-        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
+        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader') || $config->get('classmap-authoritative');
 
 
         $install
         $install
             ->setDryRun($input->getOption('dry-run'))
             ->setDryRun($input->getOption('dry-run'))
@@ -117,6 +122,7 @@ EOT
             ->setPreferSource($preferSource)
             ->setPreferSource($preferSource)
             ->setPreferDist($preferDist)
             ->setPreferDist($preferDist)
             ->setDevMode(!$input->getOption('no-dev'))
             ->setDevMode(!$input->getOption('no-dev'))
+            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
             ->setOptimizeAutoloader($optimize)
             ->setUpdate(true)
             ->setUpdate(true)

+ 40 - 12
src/Composer/Config.php

@@ -19,6 +19,8 @@ use Composer\Config\ConfigSourceInterface;
  */
  */
 class Config
 class Config
 {
 {
+    const RELATIVE_PATHS = 1;
+
     public static $defaultConfig = array(
     public static $defaultConfig = array(
         'process-timeout' => 300,
         'process-timeout' => 300,
         'use-include-path' => false,
         'use-include-path' => false,
@@ -38,6 +40,7 @@ class Config
         'discard-changes' => false,
         'discard-changes' => false,
         'autoloader-suffix' => null,
         'autoloader-suffix' => null,
         'optimize-autoloader' => false,
         'optimize-autoloader' => false,
+        'classmap-authoritative' => false,
         'prepend-autoloader' => true,
         'prepend-autoloader' => true,
         'github-domains' => array('github.com'),
         'github-domains' => array('github.com'),
         'github-expose-hostname' => true,
         'github-expose-hostname' => true,
@@ -56,6 +59,7 @@ class Config
     );
     );
 
 
     private $config;
     private $config;
+    private $baseDir;
     private $repositories;
     private $repositories;
     private $configSource;
     private $configSource;
     private $authConfigSource;
     private $authConfigSource;
@@ -64,12 +68,13 @@ class Config
     /**
     /**
      * @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings
      * @param boolean $useEnvironment Use COMPOSER_ environment variables to replace config settings
      */
      */
-    public function __construct($useEnvironment = true)
+    public function __construct($useEnvironment = true, $baseDir = null)
     {
     {
         // load defaults
         // load defaults
         $this->config = static::$defaultConfig;
         $this->config = static::$defaultConfig;
         $this->repositories = static::$defaultRepositories;
         $this->repositories = static::$defaultRepositories;
         $this->useEnvironment = (bool) $useEnvironment;
         $this->useEnvironment = (bool) $useEnvironment;
+        $this->baseDir = $baseDir;
     }
     }
 
 
     public function setConfigSource(ConfigSourceInterface $source)
     public function setConfigSource(ConfigSourceInterface $source)
@@ -121,7 +126,7 @@ class Config
                 }
                 }
 
 
                 // disable a repository with an anonymous {"name": false} repo
                 // disable a repository with an anonymous {"name": false} repo
-                if (1 === count($repository) && false === current($repository)) {
+                if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
                     unset($this->repositories[key($repository)]);
                     unset($this->repositories[key($repository)]);
                     continue;
                     continue;
                 }
                 }
@@ -149,10 +154,11 @@ class Config
      * Returns a setting
      * Returns a setting
      *
      *
      * @param  string            $key
      * @param  string            $key
+     * @param  int               $flags Options (see class constants)
      * @throws \RuntimeException
      * @throws \RuntimeException
      * @return mixed
      * @return mixed
      */
      */
-    public function get($key)
+    public function get($key, $flags = 0)
     {
     {
         switch ($key) {
         switch ($key) {
             case 'vendor-dir':
             case 'vendor-dir':
@@ -166,10 +172,14 @@ class Config
                 // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                 // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                 $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
                 $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));
 
 
-                $val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key]), '/\\');
+                $val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key], $flags), '/\\');
                 $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val);
                 $val = preg_replace('#^(\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $val);
 
 
-                return $val;
+                if (substr($key, -4) !== '-dir') {
+                    return $val;
+                }
+
+                return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val);
 
 
             case 'cache-ttl':
             case 'cache-ttl':
                 return (int) $this->config[$key];
                 return (int) $this->config[$key];
@@ -205,7 +215,7 @@ class Config
                 return (int) $this->config['cache-ttl'];
                 return (int) $this->config['cache-ttl'];
 
 
             case 'home':
             case 'home':
-                return rtrim($this->process($this->config[$key]), '/\\');
+                return rtrim($this->process($this->config[$key], $flags), '/\\');
 
 
             case 'discard-changes':
             case 'discard-changes':
                 if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
                 if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
@@ -242,17 +252,17 @@ class Config
                     return null;
                     return null;
                 }
                 }
 
 
-                return $this->process($this->config[$key]);
+                return $this->process($this->config[$key], $flags);
         }
         }
     }
     }
 
 
-    public function all()
+    public function all($flags = 0)
     {
     {
         $all = array(
         $all = array(
             'repositories' => $this->getRepositories(),
             'repositories' => $this->getRepositories(),
         );
         );
         foreach (array_keys($this->config) as $key) {
         foreach (array_keys($this->config) as $key) {
-            $all['config'][$key] = $this->get($key);
+            $all['config'][$key] = $this->get($key, $flags);
         }
         }
 
 
         return $all;
         return $all;
@@ -281,9 +291,10 @@ class Config
      * Replaces {$refs} inside a config string
      * Replaces {$refs} inside a config string
      *
      *
      * @param  string $value a config string that can contain {$refs-to-other-config}
      * @param  string $value a config string that can contain {$refs-to-other-config}
+     * @param  int    $flags Options (see class constants)
      * @return string
      * @return string
      */
      */
-    private function process($value)
+    private function process($value, $flags)
     {
     {
         $config = $this;
         $config = $this;
 
 
@@ -291,11 +302,28 @@ class Config
             return $value;
             return $value;
         }
         }
 
 
-        return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config) {
-            return $config->get($match[1]);
+        return preg_replace_callback('#\{\$(.+)\}#', function ($match) use ($config, $flags) {
+            return $config->get($match[1], $flags);
         }, $value);
         }, $value);
     }
     }
 
 
+    /**
+     * Turns relative paths in absolute paths without realpath()
+     *
+     * Since the dirs might not exist yet we can not call realpath or it will fail.
+     *
+     * @param  string $path
+     * @return string
+     */
+    private function realpath($path)
+    {
+        if (substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':') {
+            return $path;
+        }
+
+        return $this->baseDir . '/' . $path;
+    }
+
     /**
     /**
      * Reads the value of a Composer environment variable
      * Reads the value of a Composer environment variable
      *
      *

+ 1 - 0
src/Composer/Console/Application.php

@@ -184,6 +184,7 @@ class Application extends BaseApplication
                 $minSpaceFree = 1024*1024;
                 $minSpaceFree = 1024*1024;
                 if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
                 if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
                     || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
                     || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
+                    || (($df = @disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
                 ) {
                 ) {
                     $output->writeln('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>');
                     $output->writeln('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>');
                 }
                 }

+ 2 - 2
src/Composer/Downloader/FileDownloader.php

@@ -90,10 +90,10 @@ class FileDownloader implements DownloaderInterface
             } catch (\Exception $e) {
             } catch (\Exception $e) {
                 if ($this->io->isDebug()) {
                 if ($this->io->isDebug()) {
                     $this->io->write('');
                     $this->io->write('');
-                    $this->io->write('Failed: ['.get_class($e).'] '.$e->getMessage());
+                    $this->io->write('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                 } elseif (count($urls)) {
                 } elseif (count($urls)) {
                     $this->io->write('');
                     $this->io->write('');
-                    $this->io->write('    Failed, trying the next URL');
+                    $this->io->write('    Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
                 }
                 }
 
 
                 if (!count($urls)) {
                 if (!count($urls)) {

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

@@ -44,6 +44,7 @@ class EventDispatcher
     protected $io;
     protected $io;
     protected $loader;
     protected $loader;
     protected $process;
     protected $process;
+    protected $listeners;
 
 
     /**
     /**
      * Constructor.
      * Constructor.

+ 86 - 71
src/Composer/Factory.php

@@ -17,10 +17,9 @@ use Composer\Json\JsonFile;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\Package\Archiver;
 use Composer\Package\Archiver;
 use Composer\Repository\RepositoryManager;
 use Composer\Repository\RepositoryManager;
-use Composer\Repository\RepositoryInterface;
+use Composer\Repository\WritableRepositoryInterface;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\RemoteFilesystem;
 use Composer\Util\RemoteFilesystem;
-use Composer\Util\Filesystem;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Composer\EventDispatcher\EventDispatcher;
 use Composer\EventDispatcher\EventDispatcher;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\Autoload\AutoloadGenerator;
@@ -36,7 +35,6 @@ use Composer\Package\Version\VersionParser;
  */
  */
 class Factory
 class Factory
 {
 {
-
     /**
     /**
      * @return string
      * @return string
      * @throws \RuntimeException
      * @throws \RuntimeException
@@ -73,9 +71,9 @@ class Factory
                     if (!$xdgConfig) {
                     if (!$xdgConfig) {
                         $xdgConfig = $userDir . '/.config';
                         $xdgConfig = $userDir . '/.config';
                     }
                     }
-                    $home = $xdgConfig . '/composer';
-                } else {
-                    $home = $userDir . '/.composer';
+                        $home = $xdgConfig . '/composer';
+                    } else {
+                        $home = $userDir . '/.composer';
                 }
                 }
             }
             }
         }
         }
@@ -144,8 +142,10 @@ class Factory
      * @param IOInterface|null $io
      * @param IOInterface|null $io
      * @return Config
      * @return Config
      */
      */
-    public static function createConfig(IOInterface $io = null)
+    public static function createConfig(IOInterface $io = null, $cwd = null)
     {
     {
+        $cwd = $cwd ?: getcwd();
+
         // determine home and cache dirs
         // determine home and cache dirs
         $home     = self::getHomeDir();
         $home     = self::getHomeDir();
         $cacheDir = self::getCacheDir($home);
         $cacheDir = self::getCacheDir($home);
@@ -188,7 +188,7 @@ class Factory
             }
             }
         }
         }
 
 
-        $config = new Config();
+        $config = new Config(true, $cwd);
 
 
         // add dirs to the config
         // add dirs to the config
         $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir)));
         $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir)));
@@ -218,14 +218,14 @@ class Factory
 
 
     public static function getComposerFile()
     public static function getComposerFile()
     {
     {
-        return trim(getenv('COMPOSER')) ? : './composer.json';
+        return trim(getenv('COMPOSER')) ?: './composer.json';
     }
     }
 
 
     public static function createAdditionalStyles()
     public static function createAdditionalStyles()
     {
     {
         return array(
         return array(
             'highlight' => new OutputFormatterStyle('red'),
             'highlight' => new OutputFormatterStyle('red'),
-            'warning'   => new OutputFormatterStyle('black', 'yellow'),
+            'warning' => new OutputFormatterStyle('black', 'yellow'),
         );
         );
     }
     }
 
 
@@ -241,15 +241,18 @@ class Factory
                 throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
                 throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
             }
             }
             $factory = new static;
             $factory = new static;
-            $rm      = $factory->createRepositoryManager($io, $config);
+            $rm = $factory->createRepositoryManager($io, $config);
         }
         }
 
 
         foreach ($config->getRepositories() as $index => $repo) {
         foreach ($config->getRepositories() as $index => $repo) {
+            if (is_string($repo)) {
+                throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
+            }
             if (!is_array($repo)) {
             if (!is_array($repo)) {
-                throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') should be an array, ' . gettype($repo) . ' given');
+                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
             }
             }
             if (!isset($repo['type'])) {
             if (!isset($repo['type'])) {
-                throw new \UnexpectedValueException('Repository ' . $index . ' (' . json_encode($repo) . ') must have a type defined');
+                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
             }
             }
             $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
             $name = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
             while (isset($repos[$name])) {
             while (isset($repos[$name])) {
@@ -268,12 +271,15 @@ class Factory
      * @param  array|string|null         $localConfig    either a configuration array or a filename to read from, if null it will
      * @param  array|string|null         $localConfig    either a configuration array or a filename to read from, if null it will
      *                                                   read from the default filename
      *                                                   read from the default filename
      * @param  bool                      $disablePlugins Whether plugins should not be loaded
      * @param  bool                      $disablePlugins Whether plugins should not be loaded
+     * @param  bool                      $fullLoad       Whether to initialize everything or only main project stuff (used when loading the global composer)
      * @throws \InvalidArgumentException
      * @throws \InvalidArgumentException
      * @throws \UnexpectedValueException
      * @throws \UnexpectedValueException
      * @return Composer
      * @return Composer
      */
      */
-    public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false)
+    public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, $cwd = null, $fullLoad = true)
     {
     {
+        $cwd = $cwd ?: getcwd();
+
         // load Composer configuration
         // load Composer configuration
         if (null === $localConfig) {
         if (null === $localConfig) {
             $localConfig = static::getComposerFile();
             $localConfig = static::getComposerFile();
@@ -281,16 +287,16 @@ class Factory
 
 
         if (is_string($localConfig)) {
         if (is_string($localConfig)) {
             $composerFile = $localConfig;
             $composerFile = $localConfig;
-            $file         = new JsonFile($localConfig, new RemoteFilesystem($io));
+            $file = new JsonFile($localConfig, new RemoteFilesystem($io));
 
 
             if (!$file->exists()) {
             if (!$file->exists()) {
                 if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
                 if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
-                    $message = 'Composer could not find a composer.json file in ' . getcwd();
+                    $message = 'Composer could not find a composer.json file in '.$cwd;
                 } else {
                 } else {
-                    $message = 'Composer could not find the config file: ' . $localConfig;
+                    $message = 'Composer could not find the config file: '.$localConfig;
                 }
                 }
                 $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section';
                 $instructions = 'To initialize a project, please create a composer.json file as described in the http://getcomposer.org/ "Getting Started" section';
-                throw new \InvalidArgumentException($message . PHP_EOL . $instructions);
+                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
             }
             }
 
 
             $file->validateSchema(JsonFile::LAX_SCHEMA);
             $file->validateSchema(JsonFile::LAX_SCHEMA);
@@ -298,7 +304,7 @@ class Factory
         }
         }
 
 
         // Load config and override with local config/auth config
         // Load config and override with local config/auth config
-        $config = static::createConfig($io);
+        $config = static::createConfig($io, $cwd);
         $config->merge($localConfig);
         $config->merge($localConfig);
         if (isset($composerFile)) {
         if (isset($composerFile)) {
             if ($io && $io->isDebug()) {
             if ($io && $io->isDebug()) {
@@ -314,69 +320,77 @@ class Factory
             }
             }
         }
         }
 
 
-        // load auth configs into the IO instance
-        $io->loadConfiguration($config);
-
         $vendorDir = $config->get('vendor-dir');
         $vendorDir = $config->get('vendor-dir');
-        $binDir    = $config->get('bin-dir');
-
-        // setup process timeout
-        ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
+        $binDir = $config->get('bin-dir');
 
 
         // initialize composer
         // initialize composer
         $composer = new Composer();
         $composer = new Composer();
         $composer->setConfig($config);
         $composer->setConfig($config);
 
 
+        if ($fullLoad) {
+            // load auth configs into the IO instance
+            $io->loadConfiguration($config);
+
+            // setup process timeout
+            ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
+        }
+
         // initialize event dispatcher
         // initialize event dispatcher
         $dispatcher = new EventDispatcher($composer, $io);
         $dispatcher = new EventDispatcher($composer, $io);
+        $composer->setEventDispatcher($dispatcher);
 
 
         // initialize repository manager
         // initialize repository manager
         $rm = $this->createRepositoryManager($io, $config, $dispatcher);
         $rm = $this->createRepositoryManager($io, $config, $dispatcher);
+        $composer->setRepositoryManager($rm);
 
 
         // load local repository
         // load local repository
         $this->addLocalRepository($rm, $vendorDir);
         $this->addLocalRepository($rm, $vendorDir);
 
 
         // load package
         // load package
-        $parser  = new VersionParser;
+        $parser = new VersionParser;
         $loader  = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
         $loader  = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
         $package = $loader->load($localConfig);
         $package = $loader->load($localConfig);
+        $composer->setPackage($package);
 
 
         // initialize installation manager
         // initialize installation manager
         $im = $this->createInstallationManager();
         $im = $this->createInstallationManager();
-
-        // Composer composition
-        $composer->setPackage($package);
-        $composer->setRepositoryManager($rm);
         $composer->setInstallationManager($im);
         $composer->setInstallationManager($im);
 
 
-        // initialize download manager
-        $dm = $this->createDownloadManager($io, $config, $dispatcher);
-
-        $composer->setDownloadManager($dm);
-        $composer->setEventDispatcher($dispatcher);
+        if ($fullLoad) {
+            // initialize download manager
+            $dm = $this->createDownloadManager($io, $config, $dispatcher);
+            $composer->setDownloadManager($dm);
 
 
-        // initialize autoload generator
-        $generator = new AutoloadGenerator($dispatcher, $io);
-        $composer->setAutoloadGenerator($generator);
+            // initialize autoload generator
+            $generator = new AutoloadGenerator($dispatcher, $io);
+            $composer->setAutoloadGenerator($generator);
+        }
 
 
-        // add installers to the manager
+        // add installers to the manager (must happen after download manager is created since they read it out of $composer)
         $this->createDefaultInstallers($im, $composer, $io);
         $this->createDefaultInstallers($im, $composer, $io);
 
 
-        $globalRepository = $this->createGlobalRepository($config, $vendorDir);
-        $pm               = $this->createPluginManager($composer, $io, $globalRepository);
-        $composer->setPluginManager($pm);
+        if ($fullLoad) {
+            $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
+            $pm = $this->createPluginManager($io, $composer, $globalComposer);
+            $composer->setPluginManager($pm);
 
 
-        if (!$disablePlugins) {
-            $pm->loadInstalledPlugins();
-        }
+            if (!$disablePlugins) {
+                $pm->loadInstalledPlugins();
+            }
 
 
-        // purge packages if they have been deleted on the filesystem
-        $this->purgePackages($rm, $im);
+            // once we have plugins and custom installers we can
+            // purge packages from local repos if they have been deleted on the filesystem
+            if ($rm->getLocalRepository()) {
+                $this->purgePackages($rm->getLocalRepository(), $im);
+            }
+        }
 
 
         // init locker if possible
         // init locker if possible
-        if (isset($composerFile)) {
-            $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION) ? substr($composerFile, 0, -4) . 'lock' : $composerFile . '.lock';
-            $locker   = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
+        if ($fullLoad && isset($composerFile)) {
+            $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
+                ? substr($composerFile, 0, -4).'lock'
+                : $composerFile . '.lock';
+            $locker = new Package\Locker($io, new JsonFile($lockFile, new RemoteFilesystem($io, $config)), $rm, $im, md5_file($composerFile));
             $composer->setLocker($locker);
             $composer->setLocker($locker);
         }
         }
 
 
@@ -411,26 +425,29 @@ class Factory
      */
      */
     protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
     protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
     {
     {
-        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir . '/composer/installed.json')));
+        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
     }
     }
 
 
     /**
     /**
-     * @param Config $config
-     * @param string $vendorDir
-     * @return Repository\InstalledFilesystemRepository|null
+     * @param  Config        $config
+     * @return Composer|null
      */
      */
-    protected function createGlobalRepository(Config $config, $vendorDir)
+    protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins)
     {
     {
-        if ($config->get('home') == $vendorDir) {
-            return null;
+        if (realpath($config->get('home')) === getcwd()) {
+            return;
         }
         }
 
 
-        $path = $config->get('home') . '/vendor/composer/installed.json';
-        if (!file_exists($path)) {
-            return null;
+        $composer = null;
+        try {
+            $composer = self::createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), false);
+        } catch (\Exception $e) {
+            if ($io->isDebug()) {
+                $io->write('Failed to initialize global composer: '.$e->getMessage());
+            }
         }
         }
 
 
-        return new Repository\InstalledFilesystemRepository(new JsonFile($path));
+        return $composer;
     }
     }
 
 
     /**
     /**
@@ -495,14 +512,14 @@ class Factory
     }
     }
 
 
     /**
     /**
-     * @param  Composer             $composer
      * @param  IOInterface          $io
      * @param  IOInterface          $io
-     * @param  RepositoryInterface  $globalRepository
+     * @param  Composer             $composer
+     * @param  Composer             $globalComposer
      * @return Plugin\PluginManager
      * @return Plugin\PluginManager
      */
      */
-    protected function createPluginManager(Composer $composer, IOInterface $io, RepositoryInterface $globalRepository = null)
+    protected function createPluginManager(IOInterface $io, Composer $composer, Composer $globalComposer = null)
     {
     {
-        return new Plugin\PluginManager($io, $composer, $globalRepository);
+        return new Plugin\PluginManager($io, $composer, $globalComposer);
     }
     }
 
 
     /**
     /**
@@ -527,12 +544,11 @@ class Factory
     }
     }
 
 
     /**
     /**
-     * @param Repository\RepositoryManager  $rm
-     * @param Installer\InstallationManager $im
+     * @param WritableRepositoryInterface   $repo repository to purge packages from
+     * @param Installer\InstallationManager $im   manager to check whether packages are still installed
      */
      */
-    protected function purgePackages(Repository\RepositoryManager $rm, Installer\InstallationManager $im)
+    protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
     {
     {
-        $repo = $rm->getLocalRepository();
         foreach ($repo->getPackages() as $package) {
         foreach ($repo->getPackages() as $package) {
             if (!$im->isPackageInstalled($repo, $package)) {
             if (!$im->isPackageInstalled($repo, $package)) {
                 $repo->removePackage($package);
                 $repo->removePackage($package);
@@ -553,5 +569,4 @@ class Factory
 
 
         return $factory->createComposer($io, $config, $disablePlugins);
         return $factory->createComposer($io, $config, $disablePlugins);
     }
     }
-
 }
 }

+ 13 - 7
src/Composer/IO/ConsoleIO.php

@@ -96,13 +96,11 @@ class ConsoleIO extends BaseIO
     public function write($messages, $newline = true)
     public function write($messages, $newline = true)
     {
     {
         if (null !== $this->startTime) {
         if (null !== $this->startTime) {
-            $messages = (array) $messages;
-            $messages[0] = sprintf(
-                '[%.1fMB/%.2fs] %s',
-                memory_get_usage() / 1024 / 1024,
-                microtime(true) - $this->startTime,
-                $messages[0]
-            );
+            $memoryUsage = memory_get_usage() / 1024 / 1024;
+            $timeSpent = microtime(true) - $this->startTime;
+            $messages = array_map(function ($message) use ($memoryUsage, $timeSpent) {
+                return sprintf('[%.1fMB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
+            }, (array) $messages);
         }
         }
         $this->output->write($messages, $newline);
         $this->output->write($messages, $newline);
         $this->lastMessage = join($newline ? "\n" : '', (array) $messages);
         $this->lastMessage = join($newline ? "\n" : '', (array) $messages);
@@ -113,6 +111,14 @@ class ConsoleIO extends BaseIO
      */
      */
     public function overwrite($messages, $newline = true, $size = null)
     public function overwrite($messages, $newline = true, $size = null)
     {
     {
+        if (!$this->output->isDecorated()) {
+            if (!$messages) {
+                return;
+            }
+
+            return $this->write($messages, count($messages) === 1 || $newline);
+        }
+
         // messages can be an array, let's convert it to string anyway
         // messages can be an array, let's convert it to string anyway
         $messages = join($newline ? "\n" : '', (array) $messages);
         $messages = join($newline ? "\n" : '', (array) $messages);
 
 

+ 26 - 8
src/Composer/Installer.php

@@ -105,6 +105,7 @@ class Installer
     protected $dryRun = false;
     protected $dryRun = false;
     protected $verbose = false;
     protected $verbose = false;
     protected $update = false;
     protected $update = false;
+    protected $dumpAutoloader = true;
     protected $runScripts = true;
     protected $runScripts = true;
     protected $ignorePlatformReqs = false;
     protected $ignorePlatformReqs = false;
     protected $preferStable = false;
     protected $preferStable = false;
@@ -317,15 +318,17 @@ class Installer
                 }
                 }
             }
             }
 
 
-            // write autoloader
-            if ($this->optimizeAutoloader) {
-                $this->io->write('<info>Generating optimized autoload files</info>');
-            } else {
-                $this->io->write('<info>Generating autoload files</info>');
-            }
+            if ($this->dumpAutoloader) {
+                // write autoloader
+                if ($this->optimizeAutoloader) {
+                    $this->io->write('<info>Generating optimized autoload files</info>');
+                } else {
+                    $this->io->write('<info>Generating autoload files</info>');
+                }
 
 
-            $this->autoloadGenerator->setDevMode($this->devMode);
-            $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
+                $this->autoloadGenerator->setDevMode($this->devMode);
+                $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
+            }
 
 
             if ($this->runScripts) {
             if ($this->runScripts) {
                 // dispatch post event
                 // dispatch post event
@@ -1163,6 +1166,21 @@ class Installer
         return $this;
         return $this;
     }
     }
 
 
+
+    /**
+     * set whether to run autoloader or not
+     *
+     * @param boolean $dumpAutoloader
+     * @return Installer
+     */
+    public function setDumpAutoloader($dumpAutoloader = true)
+    {
+        $this->dumpAutoloader = (boolean) $dumpAutoloader;
+
+        return $this;
+    }
+
+
     /**
     /**
      * set whether to run scripts or not
      * set whether to run scripts or not
      *
      *

+ 2 - 2
src/Composer/Installer/PluginInstaller.php

@@ -61,7 +61,7 @@ class PluginInstaller extends LibraryInstaller
         }
         }
 
 
         parent::install($repo, $package);
         parent::install($repo, $package);
-        $this->composer->getPluginManager()->registerPackage($package);
+        $this->composer->getPluginManager()->registerPackage($package, true);
     }
     }
 
 
     /**
     /**
@@ -75,6 +75,6 @@ class PluginInstaller extends LibraryInstaller
         }
         }
 
 
         parent::update($repo, $initial, $target);
         parent::update($repo, $initial, $target);
-        $this->composer->getPluginManager()->registerPackage($target);
+        $this->composer->getPluginManager()->registerPackage($target, true);
     }
     }
 }
 }

+ 1 - 2
src/Composer/Json/JsonFile.php

@@ -154,8 +154,7 @@ class JsonFile
 
 
         if ($schema === self::LAX_SCHEMA) {
         if ($schema === self::LAX_SCHEMA) {
             $schemaData->additionalProperties = true;
             $schemaData->additionalProperties = true;
-            $schemaData->properties->name->required = false;
-            $schemaData->properties->description->required = false;
+            $schemaData->required = array();
         }
         }
 
 
         $validator = new Validator();
         $validator = new Validator();

+ 2 - 2
src/Composer/Json/JsonValidationException.php

@@ -21,10 +21,10 @@ class JsonValidationException extends Exception
 {
 {
     protected $errors;
     protected $errors;
 
 
-    public function __construct($message, $errors = array())
+    public function __construct($message, $errors = array(), \Exception $previous = null)
     {
     {
         $this->errors = $errors;
         $this->errors = $errors;
-        parent::__construct($message);
+        parent::__construct($message, 0, $previous);
     }
     }
 
 
     public function getErrors()
     public function getErrors()

+ 1 - 1
src/Composer/Package/LinkConstraint/MultiConstraint.php

@@ -78,6 +78,6 @@ class MultiConstraint implements LinkConstraintInterface
             $constraints[] = $constraint->__toString();
             $constraints[] = $constraint->__toString();
         }
         }
 
 
-        return '['.implode($this->conjunctive ? ', ' : ' | ', $constraints).']';
+        return '['.implode($this->conjunctive ? ' ' : ' || ', $constraints).']';
     }
     }
 }
 }

+ 9 - 1
src/Composer/Package/Loader/ArrayLoader.php

@@ -224,7 +224,7 @@ class ArrayLoader implements LoaderInterface
      */
      */
     public function getBranchAlias(array $config)
     public function getBranchAlias(array $config)
     {
     {
-        if ('dev-' !== substr($config['version'], 0, 4)
+        if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
             || !isset($config['extra']['branch-alias'])
             || !isset($config['extra']['branch-alias'])
             || !is_array($config['extra']['branch-alias'])
             || !is_array($config['extra']['branch-alias'])
         ) {
         ) {
@@ -248,6 +248,14 @@ class ArrayLoader implements LoaderInterface
                 continue;
                 continue;
             }
             }
 
 
+            // If using numeric aliases ensure the alias is a valid subversion
+            if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
+                && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
+                && (stripos($targetPrefix, $sourcePrefix) !== 0)
+            ) {
+                continue;
+            }
+
             return $validatedTargetBranch;
             return $validatedTargetBranch;
         }
         }
     }
     }

+ 1 - 1
src/Composer/Package/Loader/RootPackageLoader.php

@@ -131,7 +131,7 @@ class RootPackageLoader extends ArrayLoader
         $minimumStability = $stabilities[$minimumStability];
         $minimumStability = $stabilities[$minimumStability];
         foreach ($requires as $reqName => $reqVersion) {
         foreach ($requires as $reqName => $reqVersion) {
             // parse explicit stability flags to the most unstable
             // parse explicit stability flags to the most unstable
-            if (preg_match('{^[^,\s]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
+            if (preg_match('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $reqVersion, $match)) {
                 $name = strtolower($reqName);
                 $name = strtolower($reqName);
                 $stability = $stabilities[VersionParser::normalizeStability($match[1])];
                 $stability = $stabilities[VersionParser::normalizeStability($match[1])];
 
 

+ 11 - 0
src/Composer/Package/Loader/ValidatingArrayLoader.php

@@ -251,6 +251,17 @@ class ValidatingArrayLoader implements LoaderInterface
                     if ('-dev' !== substr($validatedTargetBranch, -4)) {
                     if ('-dev' !== substr($validatedTargetBranch, -4)) {
                         $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                         $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                         unset($this->config['extra']['branch-alias'][$sourceBranch]);
                         unset($this->config['extra']['branch-alias'][$sourceBranch]);
+
+                        continue;
+                    }
+
+                    // If using numeric aliases ensure the alias is a valid subversion
+                    if(($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
+                        && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
+                        && (stripos($targetPrefix, $sourcePrefix) !== 0)
+                    ) {
+                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
+                        unset($this->config['extra']['branch-alias'][$sourceBranch]);
                     }
                     }
                 }
                 }
             }
             }

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

@@ -169,6 +169,22 @@ class VersionParser
         throw new \UnexpectedValueException('Invalid version string "'.$version.'"'.$extraMessage);
         throw new \UnexpectedValueException('Invalid version string "'.$version.'"'.$extraMessage);
     }
     }
 
 
+    /**
+     * Extract numeric prefix from alias, if it is in numeric format, suitable for
+     * version comparison
+     *
+     * @param string $branch Branch name (e.g. 2.1.x-dev)
+     * @return string|false Numeric prefix if present (e.g. 2.1.) or false
+     */
+    public function parseNumericAliasPrefix($branch)
+    {
+        if (preg_match('/^(?P<version>(\d+\\.)*\d+)(?:\.x)?-dev$/i', $branch, $matches)) {
+            return $matches['version'].".";
+        }
+
+        return false;
+    }
+
     /**
     /**
      * Normalizes a branch name to be able to perform comparisons on it
      * Normalizes a branch name to be able to perform comparisons on it
      *
      *

+ 5 - 2
src/Composer/Plugin/PluginManager.php

@@ -190,10 +190,11 @@ class PluginManager
      * instead for BC
      * instead for BC
      *
      *
      * @param PackageInterface $package
      * @param PackageInterface $package
+     * @param bool             $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception
      *
      *
      * @throws \UnexpectedValueException
      * @throws \UnexpectedValueException
      */
      */
-    public function registerPackage(PackageInterface $package)
+    public function registerPackage(PackageInterface $package, $failOnMissingClasses = false)
     {
     {
         $oldInstallerPlugin = ($package->getType() === 'composer-installer');
         $oldInstallerPlugin = ($package->getType() === 'composer-installer');
 
 
@@ -242,10 +243,12 @@ class PluginManager
             if ($oldInstallerPlugin) {
             if ($oldInstallerPlugin) {
                 $installer = new $class($this->io, $this->composer);
                 $installer = new $class($this->io, $this->composer);
                 $this->composer->getInstallationManager()->addInstaller($installer);
                 $this->composer->getInstallationManager()->addInstaller($installer);
-            } else {
+            } elseif (class_exists($class)) {
                 $plugin = new $class();
                 $plugin = new $class();
                 $this->addPlugin($plugin);
                 $this->addPlugin($plugin);
                 $this->registeredPlugins[] = $package->getName();
                 $this->registeredPlugins[] = $package->getName();
+            } elseif ($failOnMissingClasses) {
+                throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
             }
             }
         }
         }
     }
     }

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

@@ -53,8 +53,6 @@ class ComposerRepository extends ArrayRepository
     protected $eventDispatcher;
     protected $eventDispatcher;
     protected $sourceMirrors;
     protected $sourceMirrors;
     protected $distMirrors;
     protected $distMirrors;
-    private $rawData;
-    private $minimalPackages;
     private $degradedMode = false;
     private $degradedMode = false;
     private $rootData;
     private $rootData;
 
 
@@ -206,6 +204,11 @@ class ComposerRepository extends ArrayRepository
             $this->loadProviderListings($this->loadRootServerFile());
             $this->loadProviderListings($this->loadRootServerFile());
         }
         }
 
 
+        if ($this->lazyProvidersUrl) {
+            // Can not determine list of provided packages for lazy repositories
+            return array();
+        }
+
         if ($this->providersUrl) {
         if ($this->providersUrl) {
             return array_keys($this->providerListing);
             return array_keys($this->providerListing);
         }
         }

+ 1 - 0
src/Composer/Repository/PearRepository.php

@@ -160,6 +160,7 @@ class PearRepository extends ArrayRepository
                 $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
                 $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
                 $package->setType('pear-library');
                 $package->setType('pear-library');
                 $package->setDescription($packageDefinition->getDescription());
                 $package->setDescription($packageDefinition->getDescription());
+                $package->setLicense(array($packageDefinition->getLicense()));
                 $package->setDistType('file');
                 $package->setDistType('file');
                 $package->setDistUrl($distUrl);
                 $package->setDistUrl($distUrl);
                 $package->setAutoload(array('classmap' => array('')));
                 $package->setAutoload(array('classmap' => array('')));

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

@@ -33,7 +33,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
      */
      */
     public function initialize()
     public function initialize()
     {
     {
-        preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
+        preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
         $this->owner = $match[1];
         $this->owner = $match[1];
         $this->repository = $match[2];
         $this->repository = $match[2];
         $this->originUrl = 'bitbucket.org';
         $this->originUrl = 'bitbucket.org';
@@ -143,7 +143,7 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
      */
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
     {
-        if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
             return false;
             return false;
         }
         }
 
 

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

@@ -202,7 +202,7 @@ class GitDriver extends VcsDriver
             $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir);
             $this->process->execute('git branch --no-color --no-abbrev -v', $output, $this->repoDir);
             foreach ($this->process->splitLines($output) as $branch) {
             foreach ($this->process->splitLines($output) as $branch) {
                 if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
                 if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
-                    if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
+                    if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) {
                         $branches[$match[1]] = $match[2];
                         $branches[$match[1]] = $match[2];
                     }
                     }
                 }
                 }
@@ -241,7 +241,11 @@ class GitDriver extends VcsDriver
             return false;
             return false;
         }
         }
 
 
-        // TODO try to connect to the server
+        $process = new ProcessExecutor($io);
+        if($process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url)) === 0) {
+            return true;
+        }
+
         return false;
         return false;
     }
     }
 }
 }

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

@@ -33,7 +33,7 @@ class HgBitbucketDriver extends VcsDriver
      */
      */
     public function initialize()
     public function initialize()
     {
     {
-        preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
+        preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
         $this->owner = $match[1];
         $this->owner = $match[1];
         $this->repository = $match[2];
         $this->repository = $match[2];
         $this->originUrl = 'bitbucket.org';
         $this->originUrl = 'bitbucket.org';
@@ -153,7 +153,7 @@ class HgBitbucketDriver extends VcsDriver
      */
      */
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     public static function supports(IOInterface $io, Config $config, $url, $deep = false)
     {
     {
-        if (!preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url)) {
             return false;
             return false;
         }
         }
 
 

+ 3 - 0
src/Composer/Util/Git.php

@@ -169,6 +169,9 @@ class Git
         if (getenv('GIT_WORK_TREE')) {
         if (getenv('GIT_WORK_TREE')) {
             putenv('GIT_WORK_TREE');
             putenv('GIT_WORK_TREE');
         }
         }
+
+        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
+        putenv("DYLD_LIBRARY_PATH");
     }
     }
 
 
     public static function getGitHubDomainsRegex(Config $config)
     public static function getGitHubDomainsRegex(Config $config)

+ 1 - 2
src/Composer/Util/RemoteFilesystem.php

@@ -26,7 +26,6 @@ class RemoteFilesystem
 {
 {
     private $io;
     private $io;
     private $config;
     private $config;
-    private $firstCall;
     private $bytesMax;
     private $bytesMax;
     private $originUrl;
     private $originUrl;
     private $fileUrl;
     private $fileUrl;
@@ -344,7 +343,7 @@ class RemoteFilesystem
     {
     {
         if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
         if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
             $message = "\n".'Could not fetch '.$this->fileUrl.', enter your GitHub credentials '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit');
             $message = "\n".'Could not fetch '.$this->fileUrl.', enter your GitHub credentials '.($httpStatus === 404 ? 'to access private repos' : 'to go over the API rate limit');
-            $gitHubUtil = new GitHub($this->io, $this->config, null, $this);
+            $gitHubUtil = new GitHub($this->io, $this->config, null);
             if (!$gitHubUtil->authorizeOAuth($this->originUrl)
             if (!$gitHubUtil->authorizeOAuth($this->originUrl)
                 && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
                 && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
             ) {
             ) {

+ 68 - 13
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -67,6 +67,17 @@ class AutoloadGeneratorTest extends TestCase
      */
      */
     private $eventDispatcher;
     private $eventDispatcher;
 
 
+    /**
+     * Map of setting name => return value configuration for the stub Config
+     * object.
+     *
+     * Note: must be public for compatibility with PHP 5.3 runtimes where
+     * closures cannot access private members of the classes they are created
+     * in.
+     * @var array
+     */
+    public $configValueMap;
+
     protected function setUp()
     protected function setUp()
     {
     {
         $this->fs = new Filesystem;
         $this->fs = new Filesystem;
@@ -79,18 +90,23 @@ class AutoloadGeneratorTest extends TestCase
 
 
         $this->config = $this->getMock('Composer\Config');
         $this->config = $this->getMock('Composer\Config');
 
 
-        $this->config->expects($this->at(0))
-            ->method('get')
-            ->with($this->equalTo('vendor-dir'))
-            ->will($this->returnCallback(function () use ($that) {
+        $this->configValueMap = array(
+            'vendor-dir' => function () use ($that) {
                 return $that->vendorDir;
                 return $that->vendorDir;
-            }));
+            },
+        );
 
 
-        $this->config->expects($this->at(1))
+        $this->config->expects($this->atLeastOnce())
             ->method('get')
             ->method('get')
-            ->with($this->equalTo('vendor-dir'))
-            ->will($this->returnCallback(function () use ($that) {
-                return $that->vendorDir;
+            ->will($this->returnCallback(function ($arg) use ($that) {
+                $ret = null;
+                if (isset($that->configValueMap[$arg])) {
+                    $ret = $that->configValueMap[$arg];
+                    if (is_callable($ret)) {
+                        $ret = $ret();
+                    }
+                }
+                return $ret;
             }));
             }));
 
 
         $this->origDir = getcwd();
         $this->origDir = getcwd();
@@ -483,6 +499,48 @@ class AutoloadGeneratorTest extends TestCase
             include $this->vendorDir.'/composer/autoload_classmap.php'
             include $this->vendorDir.'/composer/autoload_classmap.php'
         );
         );
         $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
         $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
+        $this->assertNotContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
+    }
+
+    public function testClassMapAutoloadingAuthoritative()
+    {
+        $package = new Package('a', '1.0', '1.0');
+
+        $packages = array();
+        $packages[] = $a = new Package('a/a', '1.0', '1.0');
+        $packages[] = $b = new Package('b/b', '1.0', '1.0');
+        $packages[] = $c = new Package('c/c', '1.0', '1.0');
+        $a->setAutoload(array('classmap' => array('')));
+        $b->setAutoload(array('classmap' => array('test.php')));
+        $c->setAutoload(array('classmap' => array('./')));
+
+        $this->repository->expects($this->once())
+            ->method('getCanonicalPackages')
+            ->will($this->returnValue($packages));
+
+        $this->configValueMap['classmap-authoritative'] = true;
+
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/composer');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/a/a/src');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/b/b');
+        $this->fs->ensureDirectoryExists($this->vendorDir.'/c/c/foo');
+        file_put_contents($this->vendorDir.'/a/a/src/a.php', '<?php class ClassMapFoo {}');
+        file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}');
+        file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}');
+
+        $this->generator->dump($this->config, $this->repository, $package, $this->im, 'composer', false, '_7');
+        $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
+        $this->assertEquals(
+            array(
+                'ClassMapBar' => $this->vendorDir.'/b/b/test.php',
+                'ClassMapBaz' => $this->vendorDir.'/c/c/foo/test.php',
+                'ClassMapFoo' => $this->vendorDir.'/a/a/src/a.php',
+            ),
+            include $this->vendorDir.'/composer/autoload_classmap.php'
+        );
+        $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
+
+        $this->assertContains('$loader->setClassMapAuthoritative(true);', file_get_contents($this->vendorDir.'/composer/autoload_real.php'));
     }
     }
 
 
     public function testFilesAutoloadGeneration()
     public function testFilesAutoloadGeneration()
@@ -829,10 +887,7 @@ EOF;
             ->method('getCanonicalPackages')
             ->method('getCanonicalPackages')
             ->will($this->returnValue(array()));
             ->will($this->returnValue(array()));
 
 
-        $this->config->expects($this->at(2))
-            ->method('get')
-            ->with($this->equalTo('use-include-path'))
-            ->will($this->returnValue(true));
+        $this->configValueMap['use-include-path'] = true;
 
 
         $this->fs->ensureDirectoryExists($this->vendorDir.'/a');
         $this->fs->ensureDirectoryExists($this->vendorDir.'/a');
 
 

+ 41 - 0
tests/Composer/Test/ConfigTest.php

@@ -97,6 +97,18 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
             ),
             ),
         );
         );
 
 
+        $data['incorrect local config does not cause ErrorException'] = array(
+            array(
+                'packagist' => array('type' => 'composer', 'url' => 'https?://packagist.org', 'allow_ssl_downgrade' => true),
+                'type' => 'vcs',
+                'url' => 'http://example.com',
+            ),
+            array(
+                'type' => 'vcs',
+                'url' => 'http://example.com',
+            ),
+        );
+
         return $data;
         return $data;
     }
     }
 
 
@@ -121,6 +133,35 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($home.'/foo', $config->get('cache-dir'));
         $this->assertEquals($home.'/foo', $config->get('cache-dir'));
     }
     }
 
 
+    public function testRealpathReplacement()
+    {
+        $config = new Config(false, '/foo/bar');
+        $config->merge(array('config' => array(
+            'bin-dir' => '$HOME/foo',
+            'cache-dir' => '/baz/',
+            'vendor-dir' => 'vendor'
+        )));
+
+        $home = rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '\\/');
+        $this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir'));
+        $this->assertEquals($home.'/foo', $config->get('bin-dir'));
+        $this->assertEquals('/baz', $config->get('cache-dir'));
+    }
+
+    public function testFetchingRelativePaths()
+    {
+        $config = new Config(false, '/foo/bar');
+        $config->merge(array('config' => array(
+            'bin-dir' => '{$vendor-dir}/foo',
+            'vendor-dir' => 'vendor'
+        )));
+
+        $this->assertEquals('/foo/bar/vendor', $config->get('vendor-dir'));
+        $this->assertEquals('/foo/bar/vendor/foo', $config->get('bin-dir'));
+        $this->assertEquals('vendor', $config->get('vendor-dir', Config::RELATIVE_PATHS));
+        $this->assertEquals('vendor/foo', $config->get('bin-dir', Config::RELATIVE_PATHS));
+    }
+
     public function testOverrideGithubProtocols()
     public function testOverrideGithubProtocols()
     {
     {
         $config = new Config(false);
         $config = new Config(false);

+ 35 - 5
tests/Composer/Test/IO/ConsoleIOTest.php

@@ -49,6 +49,30 @@ class ConsoleIOTest extends TestCase
         $consoleIO->write('some information about something', false);
         $consoleIO->write('some information about something', false);
     }
     }
 
 
+    public function testWriteWithMultipleLineStringWhenDebugging()
+    {
+        $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
+        $outputMock = $this->getMock('Symfony\Component\Console\Output\OutputInterface');
+        $outputMock->expects($this->once())
+            ->method('write')
+            ->with(
+                $this->callback(function($messages){
+                    $result = preg_match("[(.*)/(.*) First line]", $messages[0]) > 0;
+                    $result &= preg_match("[(.*)/(.*) Second line]", $messages[1]) > 0;
+                    return $result;
+                }),
+                $this->equalTo(false)
+            );
+        $helperMock = $this->getMock('Symfony\Component\Console\Helper\HelperSet');
+
+        $consoleIO = new ConsoleIO($inputMock, $outputMock, $helperMock);
+        $startTime = microtime(true);
+        $consoleIO->enableDebugging($startTime);
+
+        $example = explode('\n', 'First line\nSecond lines');
+        $consoleIO->write($example, false);
+    }
+
     public function testOverwrite()
     public function testOverwrite()
     {
     {
         $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
         $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
@@ -58,21 +82,27 @@ class ConsoleIOTest extends TestCase
             ->method('write')
             ->method('write')
             ->with($this->equalTo('something (<question>strlen = 23</question>)'));
             ->with($this->equalTo('something (<question>strlen = 23</question>)'));
         $outputMock->expects($this->at(1))
         $outputMock->expects($this->at(1))
+            ->method('isDecorated')
+            ->willReturn(true);
+        $outputMock->expects($this->at(2))
             ->method('write')
             ->method('write')
             ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
             ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
-        $outputMock->expects($this->at(2))
+        $outputMock->expects($this->at(3))
             ->method('write')
             ->method('write')
             ->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
             ->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
-        $outputMock->expects($this->at(3))
+        $outputMock->expects($this->at(4))
             ->method('write')
             ->method('write')
             ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
             ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
-        $outputMock->expects($this->at(4))
+        $outputMock->expects($this->at(5))
             ->method('write')
             ->method('write')
             ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
             ->with($this->equalTo(str_repeat("\x08", 11)), $this->equalTo(false));
-        $outputMock->expects($this->at(5))
+        $outputMock->expects($this->at(6))
+            ->method('isDecorated')
+            ->willReturn(true);
+        $outputMock->expects($this->at(7))
             ->method('write')
             ->method('write')
             ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
             ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
-        $outputMock->expects($this->at(6))
+        $outputMock->expects($this->at(8))
             ->method('write')
             ->method('write')
             ->with($this->equalTo('something longer than initial (<info>34</info>)'));
             ->with($this->equalTo('something longer than initial (<info>34</info>)'));
 
 

+ 89 - 52
tests/Composer/Test/InstallerTest.php

@@ -241,10 +241,10 @@ class InstallerTest extends TestCase
         }
         }
 
 
         $installationManager = $composer->getInstallationManager();
         $installationManager = $composer->getInstallationManager();
-        $this->assertSame($expect, implode("\n", $installationManager->getTrace()));
+        $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace()));
 
 
         if ($expectOutput) {
         if ($expectOutput) {
-            $this->assertEquals($expectOutput, $output);
+            $this->assertEquals(rtrim($expectOutput), rtrim($output));
         }
         }
     }
     }
 
 
@@ -258,21 +258,7 @@ class InstallerTest extends TestCase
                 continue;
                 continue;
             }
             }
 
 
-            $test = file_get_contents($file->getRealpath());
-
-            $content = '(?:.(?!--[A-Z]))+';
-            $pattern = '{^
-                --TEST--\s*(?P<test>.*?)\s*
-                (?:--CONDITION--\s*(?P<condition>'.$content.'))?\s*
-                --COMPOSER--\s*(?P<composer>'.$content.')\s*
-                (?:--LOCK--\s*(?P<lock>'.$content.'))?\s*
-                (?:--INSTALLED--\s*(?P<installed>'.$content.'))?\s*
-                --RUN--\s*(?P<run>.*?)\s*
-                (?:--EXPECT-LOCK--\s*(?P<expectLock>'.$content.'))?\s*
-                (?:--EXPECT-OUTPUT--\s*(?P<expectOutput>'.$content.'))?\s*
-                (?:--EXPECT-EXIT-CODE--\s*(?P<expectExitCode>\d+))?\s*
-                --EXPECT--\s*(?P<expect>.*?)\s*
-            $}xs';
+            $testData = $this->readTestFile($file, $fixturesDir);
 
 
             $installed = array();
             $installed = array();
             $installedDev = array();
             $installedDev = array();
@@ -280,48 +266,44 @@ class InstallerTest extends TestCase
             $expectLock = array();
             $expectLock = array();
             $expectExitCode = 0;
             $expectExitCode = 0;
 
 
-            if (preg_match($pattern, $test, $match)) {
-                try {
-                    $message = $match['test'];
-                    $condition = !empty($match['condition']) ? $match['condition'] : null;
-                    $composer = JsonFile::parseJson($match['composer']);
+            try {
+                $message = $testData['TEST'];
+                $condition = !empty($testData['CONDITION']) ? $testData['CONDITION'] : null;
+                $composer = JsonFile::parseJson($testData['COMPOSER']);
 
 
-                    if (isset($composer['repositories'])) {
-                        foreach ($composer['repositories'] as &$repo) {
-                            if ($repo['type'] !== 'composer') {
-                                continue;
-                            }
-
-                            // Change paths like file://foobar to file:///path/to/fixtures
-                            if (preg_match('{^file://[^/]}', $repo['url'])) {
-                                $repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
-                            }
-
-                            unset($repo);
+                if (isset($composer['repositories'])) {
+                    foreach ($composer['repositories'] as &$repo) {
+                        if ($repo['type'] !== 'composer') {
+                            continue;
                         }
                         }
-                    }
 
 
-                    if (!empty($match['lock'])) {
-                        $lock = JsonFile::parseJson($match['lock']);
-                        if (!isset($lock['hash'])) {
-                            $lock['hash'] = md5(json_encode($composer));
+                        // Change paths like file://foobar to file:///path/to/fixtures
+                        if (preg_match('{^file://[^/]}', $repo['url'])) {
+                            $repo['url'] = 'file://' . strtr($fixturesDir, '\\', '/') . '/' . substr($repo['url'], 7);
                         }
                         }
+
+                        unset($repo);
                     }
                     }
-                    if (!empty($match['installed'])) {
-                        $installed = JsonFile::parseJson($match['installed']);
-                    }
-                    $run = $match['run'];
-                    if (!empty($match['expectLock'])) {
-                        $expectLock = JsonFile::parseJson($match['expectLock']);
+                }
+
+                if (!empty($testData['LOCK'])) {
+                    $lock = JsonFile::parseJson($testData['LOCK']);
+                    if (!isset($lock['hash'])) {
+                        $lock['hash'] = md5(json_encode($composer));
                     }
                     }
-                    $expectOutput = $match['expectOutput'];
-                    $expect = $match['expect'];
-                    $expectExitCode = (int) $match['expectExitCode'];
-                } catch (\Exception $e) {
-                    die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
                 }
                 }
-            } else {
-                die(sprintf('Test "%s" is not valid, did not match the expected format.', str_replace($fixturesDir.'/', '', $file)));
+                if (!empty($testData['INSTALLED'])) {
+                    $installed = JsonFile::parseJson($testData['INSTALLED']);
+                }
+                $run = $testData['RUN'];
+                if (!empty($testData['EXPECT-LOCK'])) {
+                    $expectLock = JsonFile::parseJson($testData['EXPECT-LOCK']);
+                }
+                $expectOutput = isset($testData['EXPECT-OUTPUT']) ? $testData['EXPECT-OUTPUT'] : null;
+                $expect = $testData['EXPECT'];
+                $expectExitCode = isset($testData['EXPECT-EXIT-CODE']) ? (int) $testData['EXPECT-EXIT-CODE'] : 0;
+            } catch (\Exception $e) {
+                die(sprintf('Test "%s" is not valid: '.$e->getMessage(), str_replace($fixturesDir.'/', '', $file)));
             }
             }
 
 
             $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
             $tests[basename($file)] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $composer, $lock, $installed, $run, $expectLock, $expectOutput, $expect, $expectExitCode);
@@ -329,4 +311,59 @@ class InstallerTest extends TestCase
 
 
         return $tests;
         return $tests;
     }
     }
+
+    protected function readTestFile(\SplFileInfo $file, $fixturesDir)
+    {
+        $tokens = preg_split('#(?:^|\n*)--([A-Z-]+)--\n#', file_get_contents($file->getRealPath()), null, PREG_SPLIT_DELIM_CAPTURE);
+
+        $sectionInfo = array(
+            'TEST' => true,
+            'CONDITION' => false,
+            'COMPOSER' => true,
+            'LOCK' => false,
+            'INSTALLED'  => false,
+            'RUN' => true,
+            'EXPECT-LOCK' => false,
+            'EXPECT-OUTPUT' => false,
+            'EXPECT-EXIT-CODE' => false,
+            'EXPECT' => true,
+        );
+
+        $section = null;
+        foreach ($tokens as $i => $token)
+        {
+            if (null === $section && empty($token)) {
+                continue; // skip leading blank
+            }
+
+            if (null === $section) {
+                if (!isset($sectionInfo[$token])) {
+                    throw new \RuntimeException(sprintf(
+                        'The test file "%s" must not contain a section named "%s".',
+                        str_replace($fixturesDir.'/', '', $file),
+                        $token
+                    ));
+                }
+                $section = $token;
+                continue;
+            }
+
+            $sectionData = $token;
+
+            $data[$section] = $sectionData;
+            $section = $sectionData = null;
+        }
+
+        foreach ($sectionInfo as $section => $required) {
+            if ($required && !isset($data[$section])) {
+                throw new \RuntimeException(sprintf(
+                    'The test file "%s" must have a section named "%s".',
+                    str_replace($fixturesDir.'/', '', $file),
+                    $section
+                ));
+            }
+        }
+
+        return $data;
+    }
 }
 }

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

@@ -22,9 +22,9 @@ use Composer\IO\IOInterface;
 
 
 class FactoryMock extends Factory
 class FactoryMock extends Factory
 {
 {
-    public static function createConfig(IOInterface $io = null)
+    public static function createConfig(IOInterface $io = null, $cwd = null)
     {
     {
-        $config = new Config();
+        $config = new Config(true, $cwd);
 
 
         $config->merge(array(
         $config->merge(array(
             'config' => array('home' => sys_get_temp_dir().'/composer-test'),
             'config' => array('home' => sys_get_temp_dir().'/composer-test'),

+ 44 - 0
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -138,6 +138,50 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
 
 
         $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
         $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
         $this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
         $this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
+
+        $config = array(
+            'name' => 'A',
+            'version' => 'dev-master',
+            'extra' => array('branch-alias' => array('dev-master' => '1.0-dev')),
+        );
+
+        $package = $this->loader->load($config);
+
+        $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
+        $this->assertEquals('1.0.x-dev', $package->getPrettyVersion());
+
+        $config = array(
+            'name' => 'B',
+            'version' => '4.x-dev',
+            'extra' => array('branch-alias' => array('4.x-dev' => '4.0.x-dev')),
+        );
+
+        $package = $this->loader->load($config);
+
+        $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
+        $this->assertEquals('4.0.x-dev', $package->getPrettyVersion());
+
+        $config = array(
+            'name' => 'B',
+            'version' => '4.x-dev',
+            'extra' => array('branch-alias' => array('4.x-dev' => '4.0-dev')),
+        );
+
+        $package = $this->loader->load($config);
+
+        $this->assertInstanceOf('Composer\Package\AliasPackage', $package);
+        $this->assertEquals('4.0.x-dev', $package->getPrettyVersion());
+
+        $config = array(
+            'name' => 'C',
+            'version' => '4.x-dev',
+            'extra' => array('branch-alias' => array('4.x-dev' => '3.4.x-dev')),
+        );
+
+        $package = $this->loader->load($config);
+
+        $this->assertInstanceOf('Composer\Package\CompletePackage', $package);
+        $this->assertEquals('4.x-dev', $package->getPrettyVersion());
     }
     }
 
 
     public function testAbandoned()
     public function testAbandoned()

+ 2 - 0
tests/Composer/Test/Package/Loader/RootPackageLoaderTest.php

@@ -143,6 +143,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase
                 'foo/bar' => '~2.1.0-beta2',
                 'foo/bar' => '~2.1.0-beta2',
                 'bar/baz' => '1.0.x-dev as 1.2.0',
                 'bar/baz' => '1.0.x-dev as 1.2.0',
                 'qux/quux' => '1.0.*@rc',
                 'qux/quux' => '1.0.*@rc',
+                'zux/complex' => '~1.0,>=1.0.2@dev'
             ),
             ),
             'minimum-stability' => 'alpha',
             'minimum-stability' => 'alpha',
         ));
         ));
@@ -151,6 +152,7 @@ class RootPackageLoaderTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals(array(
         $this->assertEquals(array(
             'bar/baz' => BasePackage::STABILITY_DEV,
             'bar/baz' => BasePackage::STABILITY_DEV,
             'qux/quux' => BasePackage::STABILITY_RC,
             'qux/quux' => BasePackage::STABILITY_RC,
+            'zux/complex' => BasePackage::STABILITY_DEV,
         ), $package->getStabilityFlags());
         ), $package->getStabilityFlags());
     }
     }
 }
 }

+ 29 - 0
tests/Composer/Test/Package/Loader/ValidatingArrayLoaderTest.php

@@ -140,6 +140,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
                         'branch-alias' => array(
                         'branch-alias' => array(
                             'dev-master' => '2.0-dev',
                             'dev-master' => '2.0-dev',
                             'dev-old' => '1.0.x-dev',
                             'dev-old' => '1.0.x-dev',
+                            '3.x-dev' => '3.1.x-dev'
                         ),
                         ),
                     ),
                     ),
                     'bin' => array(
                     'bin' => array(
@@ -324,6 +325,34 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
                 ),
                 ),
                 false
                 false
             ),
             ),
+            array(
+                array(
+                    'name' => 'foo/bar',
+                    'extra' => array(
+                        'branch-alias' => array(
+                            '5.x-dev' => '3.1.x-dev'
+                        ),
+                    )
+                ),
+                array(
+                    'extra.branch-alias.5.x-dev : the target branch (3.1.x-dev) is not a valid numeric alias for this version'
+                ),
+                false
+            ),
+            array(
+                array(
+                    'name' => 'foo/bar',
+                    'extra' => array(
+                        'branch-alias' => array(
+                            '5.x-dev' => '3.1-dev'
+                        ),
+                    )
+                ),
+                array(
+                    'extra.branch-alias.5.x-dev : the target branch (3.1-dev) is not a valid numeric alias for this version'
+                ),
+                false
+            ),
         );
         );
     }
     }
 }
 }

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

@@ -67,6 +67,29 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
         return array_map($createPackage, $data);
         return array_map($createPackage, $data);
     }
     }
 
 
+    /**
+     * @dataProvider numericAliasVersions
+     */
+    public function testParseNumericAliasPrefix($input, $expected)
+    {
+        $parser = new VersionParser;
+        $this->assertSame($expected, $parser->parseNumericAliasPrefix($input));
+    }
+
+    public function numericAliasVersions()
+    {
+        return array(
+            array('0.x-dev',        '0.'),
+            array('1.0.x-dev',      '1.0.'),
+            array('1.x-dev',        '1.'),
+            array('1.2.x-dev',      '1.2.'),
+            array('1.2-dev',        '1.2.'),
+            array('1-dev',          '1.'),
+            array('dev-develop',    false),
+            array('dev-master',     false),
+        );
+    }
+
     /**
     /**
      * @dataProvider successfulNormalizedVersions
      * @dataProvider successfulNormalizedVersions
      */
      */

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

@@ -113,8 +113,12 @@ class VersionSelectorTest extends \PHPUnit_Framework_TestCase
             array('3.1.2-dev', true, 'dev', '3.1.2-dev'),
             array('3.1.2-dev', true, 'dev', '3.1.2-dev'),
             // dev packages with alias inherit the alias
             // dev packages with alias inherit the alias
             array('dev-master', true, 'dev', '~2.1@dev', '2.1.x-dev'),
             array('dev-master', true, 'dev', '~2.1@dev', '2.1.x-dev'),
+            array('dev-master', true, 'dev', '~2.1@dev', '2.1-dev'),
             array('dev-master', true, 'dev', '~2.1@dev', '2.1.3.x-dev'),
             array('dev-master', true, 'dev', '~2.1@dev', '2.1.3.x-dev'),
             array('dev-master', true, 'dev', '~2.0@dev', '2.x-dev'),
             array('dev-master', true, 'dev', '~2.0@dev', '2.x-dev'),
+            // numeric alias
+            array('3.x-dev', true, 'dev', '~3.0@dev', '3.0.x-dev'),
+            array('3.x-dev', true, 'dev', '~3.0@dev', '3.0-dev'),
         );
         );
     }
     }
 
 

+ 8 - 0
tests/Composer/Test/Repository/ArtifactRepositoryTest.php

@@ -19,6 +19,14 @@ use Composer\Package\BasePackage;
 
 
 class ArtifactRepositoryTest extends TestCase
 class ArtifactRepositoryTest extends TestCase
 {
 {
+    public function setUp()
+    {
+        parent::setUp();
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('You need the zip extension to run this test.');
+        }
+    }
+
     public function testExtractsConfigsFromZipArchives()
     public function testExtractsConfigsFromZipArchives()
     {
     {
         $expectedPackages = array(
         $expectedPackages = array(

+ 1 - 0
tests/Composer/Test/Repository/Pear/ChannelReaderTest.php

@@ -121,6 +121,7 @@ class ChannelReaderTest extends TestCase
         $expectedPackage->setType('pear-library');
         $expectedPackage->setType('pear-library');
         $expectedPackage->setDistType('file');
         $expectedPackage->setDistType('file');
         $expectedPackage->setDescription('description');
         $expectedPackage->setDescription('description');
+        $expectedPackage->setLicense(array('license'));
         $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz");
         $expectedPackage->setDistUrl("http://test.loc/get/sample-1.0.0.1.tgz");
         $expectedPackage->setAutoload(array('classmap' => array('')));
         $expectedPackage->setAutoload(array('classmap' => array('')));
         $expectedPackage->setIncludePaths(array('/'));
         $expectedPackage->setIncludePaths(array('/'));

+ 0 - 7
tests/Composer/Test/Repository/PearRepositoryTest.php

@@ -84,13 +84,6 @@ class PearRepositoryTest extends TestCase
     public function repositoryDataProvider()
     public function repositoryDataProvider()
     {
     {
         return array(
         return array(
-           array(
-                'pear.phpunit.de',
-                array(
-                    array('name' => 'pear-pear.phpunit.de/PHPUnit_MockObject', 'version' => '1.1.1'),
-                    array('name' => 'pear-pear.phpunit.de/PHPUnit', 'version' => '3.6.10'),
-                )
-            ),
             array(
             array(
                 'pear.php.net',
                 'pear.php.net',
                 array(
                 array(