Browse Source

Merge remote-tracking branch 'parent/master'

Conflicts:
	src/Composer/Factory.php
Nicolas Toniazzi 10 years ago
parent
commit
865eab602f
63 changed files with 776 additions and 293 deletions
  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`
 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)
 ----------------------------------------
 
@@ -55,20 +43,6 @@ Updating Composer
 Running `php composer.phar self-update` or equivalent will update a phar
 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
 ---------

+ 1 - 1
composer.json

@@ -23,7 +23,7 @@
     },
     "require": {
         "php": ">=5.3.2",
-        "justinrainbow/json-schema": "~1.1",
+        "justinrainbow/json-schema": "~1.3",
         "seld/jsonlint": "~1.0",
         "symfony/console": "~2.3",
         "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",
         "This file is @generated automatically"
     ],
-    "hash": "0430383b5ba00e406ce4a44253f49fe7",
+    "hash": "2bc9cc8aa706b68d611d7058e4eb8de7",
     "packages": [
         {
             "name": "justinrainbow/json-schema",
@@ -327,16 +327,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "2.0.13",
+            "version": "2.0.14",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5"
+                "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94"
             },
             "dist": {
                 "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": ""
             },
             "require": {
@@ -388,7 +388,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2014-12-03 06:41:44"
+            "time": "2014-12-26 13:28:33"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -574,16 +574,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "4.4.0",
+            "version": "4.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "bbe7bcb83b6ec1a9eaabbe1b70d4795027c53ee0"
+                "reference": "6a5e49a86ce5e33b8d0657abe145057fc513543a"
             },
             "dist": {
                 "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": ""
             },
             "require": {
@@ -641,7 +641,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2014-12-05 06:49:03"
+            "time": "2014-12-28 07:57:05"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
@@ -982,16 +982,16 @@
         },
         {
             "name": "sebastian/version",
-            "version": "1.0.3",
+            "version": "1.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/version.git",
-                "reference": "b6e1f0cf6b9e1ec409a0d3e2f2a5fb0998e36b43"
+                "reference": "a77d9123f8e809db3fbdea15038c27a95da4058b"
             },
             "dist": {
                 "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": ""
             },
             "type": "library",
@@ -1013,7 +1013,7 @@
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "homepage": "https://github.com/sebastianbergmann/version",
-            "time": "2014-03-07 15:35:33"
+            "time": "2014-12-15 14:25:24"
         },
         {
             "name": "symfony/yaml",
@@ -1067,6 +1067,7 @@
     "minimum-stability": "stable",
     "stability-flags": [],
     "prefer-stable": false,
+    "prefer-lowest": false,
     "platform": {
         "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
 > 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`.
 
 ## Installation - Windows

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

@@ -2,7 +2,7 @@
 
 ## 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
 
@@ -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
 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.
 
 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
 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:
 

+ 15 - 8
doc/03-cli.md

@@ -87,7 +87,8 @@ resolution.
   installing a package, you can use `--dry-run`. This will simulate the
   installation and show you what would happen.
 * **--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-plugins:** Disables plugins.
 * **--no-progress:** Removes the progress display that can mess with some
@@ -129,7 +130,8 @@ php composer.phar update vendor/*
   fulfill these.
 * **--dry-run:** Simulate the command without actually doing anything.
 * **--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-plugins:** Disables plugins.
 * **--no-progress:** Removes the progress display that can mess with some
@@ -397,16 +399,18 @@ options.
 ### Options
 
 * **--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
-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`.
 * **--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
- 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
 
@@ -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
   project. This is mostly useful if you run the command in non-interactive
   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
 

+ 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).
 
 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:
 
@@ -67,6 +67,7 @@ Examples:
 - 1.0.0-alpha3
 - 1.0.0-beta2
 - 1.0.0-RC5
+- v2.0.4-p1
 
 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
@@ -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
 simply list it in `provide`.
 
-### suggest
+#### suggest
 
 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
@@ -790,6 +791,9 @@ The following options are supported:
   the generated Composer autoloader. When null a random one will be generated.
 * **optimize-autoloader** Defaults to `false`. Always optimize when dumping
   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 mode. This is used for GitHub Enterprise setups.
 * **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
 {
     "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
 `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`
 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
 {
-    "basic-auth": [
+    "basic-auth": {
         "repo.example1.org": {
             "username": "my-username1",
             "password": "my-secret-password1"
@@ -49,7 +49,7 @@ username/password pairs, for example:
             "username": "my-username2",
             "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)
 
 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.
 
 ## 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",
     "type": "object",
     "additionalProperties": false,
+    "required": [ "name", "description" ],
     "properties": {
         "name": {
             "type": "string",
-            "description": "Package name, including 'vendor-name/' prefix.",
-            "required": true
+            "description": "Package name, including 'vendor-name/' prefix."
         },
         "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.",
@@ -18,8 +19,7 @@
         },
         "description": {
             "type": "string",
-            "description": "Short package description.",
-            "required": true
+            "description": "Short package description."
         },
         "keywords": {
             "type": "array",
@@ -51,11 +51,11 @@
             "items": {
                 "type": "object",
                 "additionalProperties": false,
+                "required": [ "name"],
                 "properties": {
                     "name": {
                         "type": "string",
-                        "description": "Full name of the author.",
-                        "required": true
+                        "description": "Full name of the author."
                     },
                     "email": {
                         "type": "string",
@@ -197,6 +197,10 @@
                     "type": "boolean",
                     "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": {
                     "type": "array",
                     "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')));
         $useGlobalIncludePath = (bool) $config->get('use-include-path');
         $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
+        $classMapAuthoritative = $config->get('classmap-authoritative');
         $targetDir = $vendorPath.'/'.$targetDir;
         $filesystem->ensureDirectoryExists($targetDir);
 
@@ -226,7 +227,7 @@ EOF;
             file_put_contents($targetDir.'/autoload_files.php', $includeFilesFile);
         }
         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
         // to work around https://bugs.php.net/bug.php?id=64634
@@ -443,7 +444,7 @@ return ComposerAutoloaderInit$suffix::getLoader();
 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
         // when APC has been fixed:
@@ -520,6 +521,13 @@ PSR4;
 CLASSMAP;
         }
 
+        if ($classMapAuthoritative) {
+            $file .= <<<'CLASSMAPAUTHORITATIVE'
+        $loader->setClassMapAuthoritative(true);
+
+CLASSMAPAUTHORITATIVE;
+        }
+
         if ($useGlobalIncludePath) {
             $file .= <<<'INCLUDEPATH'
         $loader->setUseIncludePath(true);

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

@@ -54,6 +54,8 @@ class ClassLoader
     private $useIncludePath = false;
     private $classMap = array();
 
+    private $classMapAuthoritative = false;
+
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
@@ -248,6 +250,27 @@ class ClassLoader
         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.
      *
@@ -299,6 +322,9 @@ class ClassLoader
         if (isset($this->classMap[$class])) {
             return $this->classMap[$class];
         }
+        if ($this->classMapAuthoritative) {
+            return false;
+        }
 
         $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\IO\IOInterface;
 use Composer\IO\NullIO;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Command\Command as BaseCommand;
 
 /**
@@ -102,4 +104,16 @@ abstract class Command extends BaseCommand
     {
         $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('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('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-value', InputArgument::IS_ARRAY, 'Setting value'),
             ))
@@ -99,6 +100,8 @@ EOT
      */
     protected function initialize(InputInterface $input, OutputInterface $output)
     {
+        parent::initialize($input, $output);
+
         if ($input->getOption('global') && 'composer.json' !== $input->getOption('file')) {
             throw new \RuntimeException('--file and --global can not be combined');
         }
@@ -134,7 +137,7 @@ EOT
         }
 
         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;
             } elseif (isset($data['config'][$settingKey])) {
-                $value = $data['config'][$settingKey];
+                $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
             } else {
                 throw new \RuntimeException($settingKey.' is not defined');
             }
@@ -322,6 +325,7 @@ EOT
             ),
             'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }),
             'optimize-autoloader' => array($booleanValidator, $booleanNormalizer),
+            'classmap-authoritative' => array($booleanValidator, $booleanNormalizer),
             'prepend-autoloader' => 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('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('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
             ))
             ->setHelp(<<<EOT
 The <info>create-project</info> command creates a new project from a given
@@ -125,11 +126,12 @@ EOT
             $input->getOption('keep-vcs'),
             $input->getOption('no-progress'),
             $input->getOption('no-install'),
+            $input->getOption('ignore-platform-reqs'),
             $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();
 
@@ -159,7 +161,8 @@ EOT
             $installer->setPreferSource($preferSource)
                 ->setPreferDist($preferDist)
                 ->setDevMode($installDevPackages)
-                ->setRunScripts( ! $noScripts);
+                ->setRunScripts(!$noScripts)
+                ->setIgnorePlatformRequirements($ignorePlatformReqs);
 
             if ($disablePlugins) {
                 $installer->disablePlugins();

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

@@ -52,7 +52,7 @@ EOT
         $package = $composer->getPackage();
         $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) {
             $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(
                 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('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
             ))
             ->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.
 
 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
             );
     }
@@ -55,7 +57,7 @@ EOT
      */
     protected function execute(InputInterface $input, OutputInterface $output)
     {
-        $repo = $this->initializeRepo($input, $output);
+        $repo = $this->initializeRepo();
         $return = 0;
 
         foreach ($input->getArgument('packages') as $packageName) {
@@ -81,7 +83,11 @@ EOT
                 continue;
             }
 
-            $this->openBrowser($url);
+            if ($input->getOption('show')) {
+                $output->writeln(sprintf('<info>%s</info>', $url));
+            } else {
+                $this->openBrowser($url);
+            }
         }
 
         return $return;
@@ -138,13 +144,11 @@ EOT
     }
 
     /**
-     * initializes the repo
+     * Initializes the repo
      *
-     * @param  InputInterface      $input
-     * @param  OutputInterface     $output
      * @return CompositeRepository
      */
-    private function initializeRepo(InputInterface $input, OutputInterface $output)
+    private function initializeRepo()
     {
         $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-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-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-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.'),
@@ -74,6 +75,10 @@ EOT
             $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->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $io = $this->getIO();
@@ -105,7 +110,7 @@ EOT
             $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
             ->setDryRun($input->getOption('dry-run'))
@@ -113,6 +118,7 @@ EOT
             ->setPreferSource($preferSource)
             ->setPreferDist($preferDist)
             ->setDevMode(!$input->getOption('no-dev'))
+            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
             ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))

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

@@ -38,7 +38,7 @@ class RequireCommand extends InitCommand
             ->setName('require')
             ->setDescription('Adds required packages to your composer.json and installs them')
             ->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('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.'),
@@ -50,7 +50,9 @@ class RequireCommand extends InitCommand
                 new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
             ))
             ->setHelp(<<<EOT
-The require command adds required packages to your composer.json and installs them
+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
 

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

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

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

@@ -41,6 +41,7 @@ class ShowCommand extends Command
     {
         $this
             ->setName('show')
+            ->setAliases(array('info'))
             ->setDescription('Show information about packages')
             ->setDefinition(array(
                 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('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-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-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.'),
@@ -78,6 +79,10 @@ EOT
             $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->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));
         $io = $this->getIO();
@@ -109,7 +114,7 @@ EOT
             $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
             ->setDryRun($input->getOption('dry-run'))
@@ -117,6 +122,7 @@ EOT
             ->setPreferSource($preferSource)
             ->setPreferDist($preferDist)
             ->setDevMode(!$input->getOption('no-dev'))
+            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
             ->setRunScripts(!$input->getOption('no-scripts'))
             ->setOptimizeAutoloader($optimize)
             ->setUpdate(true)

+ 40 - 12
src/Composer/Config.php

@@ -19,6 +19,8 @@ use Composer\Config\ConfigSourceInterface;
  */
 class Config
 {
+    const RELATIVE_PATHS = 1;
+
     public static $defaultConfig = array(
         'process-timeout' => 300,
         'use-include-path' => false,
@@ -38,6 +40,7 @@ class Config
         'discard-changes' => false,
         'autoloader-suffix' => null,
         'optimize-autoloader' => false,
+        'classmap-authoritative' => false,
         'prepend-autoloader' => true,
         'github-domains' => array('github.com'),
         'github-expose-hostname' => true,
@@ -56,6 +59,7 @@ class Config
     );
 
     private $config;
+    private $baseDir;
     private $repositories;
     private $configSource;
     private $authConfigSource;
@@ -64,12 +68,13 @@ class Config
     /**
      * @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
         $this->config = static::$defaultConfig;
         $this->repositories = static::$defaultRepositories;
         $this->useEnvironment = (bool) $useEnvironment;
+        $this->baseDir = $baseDir;
     }
 
     public function setConfigSource(ConfigSourceInterface $source)
@@ -121,7 +126,7 @@ class Config
                 }
 
                 // 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)]);
                     continue;
                 }
@@ -149,10 +154,11 @@ class Config
      * Returns a setting
      *
      * @param  string            $key
+     * @param  int               $flags Options (see class constants)
      * @throws \RuntimeException
      * @return mixed
      */
-    public function get($key)
+    public function get($key, $flags = 0)
     {
         switch ($key) {
             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
                 $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);
 
-                return $val;
+                if (substr($key, -4) !== '-dir') {
+                    return $val;
+                }
+
+                return ($flags & self::RELATIVE_PATHS == 1) ? $val : $this->realpath($val);
 
             case 'cache-ttl':
                 return (int) $this->config[$key];
@@ -205,7 +215,7 @@ class Config
                 return (int) $this->config['cache-ttl'];
 
             case 'home':
-                return rtrim($this->process($this->config[$key]), '/\\');
+                return rtrim($this->process($this->config[$key], $flags), '/\\');
 
             case 'discard-changes':
                 if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
@@ -242,17 +252,17 @@ class Config
                     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(
             'repositories' => $this->getRepositories(),
         );
         foreach (array_keys($this->config) as $key) {
-            $all['config'][$key] = $this->get($key);
+            $all['config'][$key] = $this->get($key, $flags);
         }
 
         return $all;
@@ -281,9 +291,10 @@ class Config
      * Replaces {$refs} inside a config string
      *
      * @param  string $value a config string that can contain {$refs-to-other-config}
+     * @param  int    $flags Options (see class constants)
      * @return string
      */
-    private function process($value)
+    private function process($value, $flags)
     {
         $config = $this;
 
@@ -291,11 +302,28 @@ class Config
             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);
     }
 
+    /**
+     * 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
      *

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

@@ -184,6 +184,7 @@ class Application extends BaseApplication
                 $minSpaceFree = 1024*1024;
                 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 = 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>');
                 }

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

@@ -90,10 +90,10 @@ class FileDownloader implements DownloaderInterface
             } catch (\Exception $e) {
                 if ($this->io->isDebug()) {
                     $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)) {
                     $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)) {

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

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

+ 86 - 71
src/Composer/Factory.php

@@ -17,10 +17,9 @@ use Composer\Json\JsonFile;
 use Composer\IO\IOInterface;
 use Composer\Package\Archiver;
 use Composer\Repository\RepositoryManager;
-use Composer\Repository\RepositoryInterface;
+use Composer\Repository\WritableRepositoryInterface;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\RemoteFilesystem;
-use Composer\Util\Filesystem;
 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
 use Composer\EventDispatcher\EventDispatcher;
 use Composer\Autoload\AutoloadGenerator;
@@ -36,7 +35,6 @@ use Composer\Package\Version\VersionParser;
  */
 class Factory
 {
-
     /**
      * @return string
      * @throws \RuntimeException
@@ -73,9 +71,9 @@ class Factory
                     if (!$xdgConfig) {
                         $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
      * @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
         $home     = self::getHomeDir();
         $cacheDir = self::getCacheDir($home);
@@ -188,7 +188,7 @@ class Factory
             }
         }
 
-        $config = new Config();
+        $config = new Config(true, $cwd);
 
         // add dirs to the config
         $config->merge(array('config' => array('home' => $home, 'cache-dir' => $cacheDir, 'data-dir' => $dataDir)));
@@ -218,14 +218,14 @@ class Factory
 
     public static function getComposerFile()
     {
-        return trim(getenv('COMPOSER')) ? : './composer.json';
+        return trim(getenv('COMPOSER')) ?: './composer.json';
     }
 
     public static function createAdditionalStyles()
     {
         return array(
             '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');
             }
             $factory = new static;
-            $rm      = $factory->createRepositoryManager($io, $config);
+            $rm = $factory->createRepositoryManager($io, $config);
         }
 
         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)) {
-                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'])) {
-                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;
             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
      *                                                   read from the default filename
      * @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 \UnexpectedValueException
      * @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
         if (null === $localConfig) {
             $localConfig = static::getComposerFile();
@@ -281,16 +287,16 @@ class Factory
 
         if (is_string($localConfig)) {
             $composerFile = $localConfig;
-            $file         = new JsonFile($localConfig, new RemoteFilesystem($io));
+            $file = new JsonFile($localConfig, new RemoteFilesystem($io));
 
             if (!$file->exists()) {
                 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 {
-                    $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';
-                throw new \InvalidArgumentException($message . PHP_EOL . $instructions);
+                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
             }
 
             $file->validateSchema(JsonFile::LAX_SCHEMA);
@@ -298,7 +304,7 @@ class Factory
         }
 
         // Load config and override with local config/auth config
-        $config = static::createConfig($io);
+        $config = static::createConfig($io, $cwd);
         $config->merge($localConfig);
         if (isset($composerFile)) {
             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');
-        $binDir    = $config->get('bin-dir');
-
-        // setup process timeout
-        ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
+        $binDir = $config->get('bin-dir');
 
         // initialize composer
         $composer = new Composer();
         $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
         $dispatcher = new EventDispatcher($composer, $io);
+        $composer->setEventDispatcher($dispatcher);
 
         // initialize repository manager
         $rm = $this->createRepositoryManager($io, $config, $dispatcher);
+        $composer->setRepositoryManager($rm);
 
         // load local repository
         $this->addLocalRepository($rm, $vendorDir);
 
         // load package
-        $parser  = new VersionParser;
+        $parser = new VersionParser;
         $loader  = new Package\Loader\RootPackageLoader($rm, $config, $parser, new ProcessExecutor($io));
         $package = $loader->load($localConfig);
+        $composer->setPackage($package);
 
         // initialize installation manager
         $im = $this->createInstallationManager();
-
-        // Composer composition
-        $composer->setPackage($package);
-        $composer->setRepositoryManager($rm);
         $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);
 
-        $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
-        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);
         }
 
@@ -411,26 +425,29 @@ class Factory
      */
     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  RepositoryInterface  $globalRepository
+     * @param  Composer             $composer
+     * @param  Composer             $globalComposer
      * @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) {
             if (!$im->isPackageInstalled($repo, $package)) {
                 $repo->removePackage($package);
@@ -553,5 +569,4 @@ class Factory
 
         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)
     {
         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->lastMessage = join($newline ? "\n" : '', (array) $messages);
@@ -113,6 +111,14 @@ class ConsoleIO extends BaseIO
      */
     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 = join($newline ? "\n" : '', (array) $messages);
 

+ 26 - 8
src/Composer/Installer.php

@@ -105,6 +105,7 @@ class Installer
     protected $dryRun = false;
     protected $verbose = false;
     protected $update = false;
+    protected $dumpAutoloader = true;
     protected $runScripts = true;
     protected $ignorePlatformReqs = 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) {
                 // dispatch post event
@@ -1163,6 +1166,21 @@ class Installer
         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
      *

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

@@ -61,7 +61,7 @@ class PluginInstaller extends LibraryInstaller
         }
 
         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);
-        $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) {
             $schemaData->additionalProperties = true;
-            $schemaData->properties->name->required = false;
-            $schemaData->properties->description->required = false;
+            $schemaData->required = array();
         }
 
         $validator = new Validator();

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

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

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

@@ -78,6 +78,6 @@ class MultiConstraint implements LinkConstraintInterface
             $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)
     {
-        if ('dev-' !== substr($config['version'], 0, 4)
+        if (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
             || !isset($config['extra']['branch-alias'])
             || !is_array($config['extra']['branch-alias'])
         ) {
@@ -248,6 +248,14 @@ class ArrayLoader implements LoaderInterface
                 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;
         }
     }

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

@@ -131,7 +131,7 @@ class RootPackageLoader extends ArrayLoader
         $minimumStability = $stabilities[$minimumStability];
         foreach ($requires as $reqName => $reqVersion) {
             // 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);
                 $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)) {
                         $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]);
+
+                        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);
     }
 
+    /**
+     * 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
      *

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

@@ -190,10 +190,11 @@ class PluginManager
      * instead for BC
      *
      * @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
      */
-    public function registerPackage(PackageInterface $package)
+    public function registerPackage(PackageInterface $package, $failOnMissingClasses = false)
     {
         $oldInstallerPlugin = ($package->getType() === 'composer-installer');
 
@@ -242,10 +243,12 @@ class PluginManager
             if ($oldInstallerPlugin) {
                 $installer = new $class($this->io, $this->composer);
                 $this->composer->getInstallationManager()->addInstaller($installer);
-            } else {
+            } elseif (class_exists($class)) {
                 $plugin = new $class();
                 $this->addPlugin($plugin);
                 $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 $sourceMirrors;
     protected $distMirrors;
-    private $rawData;
-    private $minimalPackages;
     private $degradedMode = false;
     private $rootData;
 
@@ -206,6 +204,11 @@ class ComposerRepository extends ArrayRepository
             $this->loadProviderListings($this->loadRootServerFile());
         }
 
+        if ($this->lazyProvidersUrl) {
+            // Can not determine list of provided packages for lazy repositories
+            return array();
+        }
+
         if ($this->providersUrl) {
             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->setType('pear-library');
                 $package->setDescription($packageDefinition->getDescription());
+                $package->setLicense(array($packageDefinition->getLicense()));
                 $package->setDistType('file');
                 $package->setDistUrl($distUrl);
                 $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()
     {
-        preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
+        preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
         $this->owner = $match[1];
         $this->repository = $match[2];
         $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)
     {
-        if (!preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
+        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url)) {
             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);
             foreach ($this->process->splitLines($output) as $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];
                     }
                 }
@@ -241,7 +241,11 @@ class GitDriver extends VcsDriver
             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;
     }
 }

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

@@ -33,7 +33,7 @@ class HgBitbucketDriver extends VcsDriver
      */
     public function initialize()
     {
-        preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
+        preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
         $this->owner = $match[1];
         $this->repository = $match[2];
         $this->originUrl = 'bitbucket.org';
@@ -153,7 +153,7 @@ class HgBitbucketDriver extends VcsDriver
      */
     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;
         }
 

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

@@ -169,6 +169,9 @@ class Git
         if (getenv('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)

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

@@ -26,7 +26,6 @@ class RemoteFilesystem
 {
     private $io;
     private $config;
-    private $firstCall;
     private $bytesMax;
     private $originUrl;
     private $fileUrl;
@@ -344,7 +343,7 @@ class RemoteFilesystem
     {
         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');
-            $gitHubUtil = new GitHub($this->io, $this->config, null, $this);
+            $gitHubUtil = new GitHub($this->io, $this->config, null);
             if (!$gitHubUtil->authorizeOAuth($this->originUrl)
                 && (!$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;
 
+    /**
+     * 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()
     {
         $this->fs = new Filesystem;
@@ -79,18 +90,23 @@ class AutoloadGeneratorTest extends TestCase
 
         $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;
-            }));
+            },
+        );
 
-        $this->config->expects($this->at(1))
+        $this->config->expects($this->atLeastOnce())
             ->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();
@@ -483,6 +499,48 @@ class AutoloadGeneratorTest extends TestCase
             include $this->vendorDir.'/composer/autoload_classmap.php'
         );
         $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()
@@ -829,10 +887,7 @@ EOF;
             ->method('getCanonicalPackages')
             ->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');
 

+ 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;
     }
 
@@ -121,6 +133,35 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
         $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()
     {
         $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);
     }
 
+    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()
     {
         $inputMock = $this->getMock('Symfony\Component\Console\Input\InputInterface');
@@ -58,21 +82,27 @@ class ConsoleIOTest extends TestCase
             ->method('write')
             ->with($this->equalTo('something (<question>strlen = 23</question>)'));
         $outputMock->expects($this->at(1))
+            ->method('isDecorated')
+            ->willReturn(true);
+        $outputMock->expects($this->at(2))
             ->method('write')
             ->with($this->equalTo(str_repeat("\x08", 23)), $this->equalTo(false));
-        $outputMock->expects($this->at(2))
+        $outputMock->expects($this->at(3))
             ->method('write')
             ->with($this->equalTo('shorter (<comment>12</comment>)'), $this->equalTo(false));
-        $outputMock->expects($this->at(3))
+        $outputMock->expects($this->at(4))
             ->method('write')
             ->with($this->equalTo(str_repeat(' ', 11)), $this->equalTo(false));
-        $outputMock->expects($this->at(4))
+        $outputMock->expects($this->at(5))
             ->method('write')
             ->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')
             ->with($this->equalTo(str_repeat("\x08", 12)), $this->equalTo(false));
-        $outputMock->expects($this->at(6))
+        $outputMock->expects($this->at(8))
             ->method('write')
             ->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();
-        $this->assertSame($expect, implode("\n", $installationManager->getTrace()));
+        $this->assertSame(rtrim($expect), implode("\n", $installationManager->getTrace()));
 
         if ($expectOutput) {
-            $this->assertEquals($expectOutput, $output);
+            $this->assertEquals(rtrim($expectOutput), rtrim($output));
         }
     }
 
@@ -258,21 +258,7 @@ class InstallerTest extends TestCase
                 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();
             $installedDev = array();
@@ -280,48 +266,44 @@ class InstallerTest extends TestCase
             $expectLock = array();
             $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);
@@ -329,4 +311,59 @@ class InstallerTest extends TestCase
 
         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
 {
-    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' => 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->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()

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

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

@@ -140,6 +140,7 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
                         'branch-alias' => array(
                             'dev-master' => '2.0-dev',
                             'dev-old' => '1.0.x-dev',
+                            '3.x-dev' => '3.1.x-dev'
                         ),
                     ),
                     'bin' => array(
@@ -324,6 +325,34 @@ class ValidatingArrayLoaderTest extends \PHPUnit_Framework_TestCase
                 ),
                 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);
     }
 
+    /**
+     * @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
      */

+ 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'),
             // 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-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'),
+            // 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
 {
+    public function setUp()
+    {
+        parent::setUp();
+        if (!extension_loaded('zip')) {
+            $this->markTestSkipped('You need the zip extension to run this test.');
+        }
+    }
+
     public function testExtractsConfigsFromZipArchives()
     {
         $expectedPackages = array(

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

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

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

@@ -84,13 +84,6 @@ class PearRepositoryTest extends TestCase
     public function repositoryDataProvider()
     {
         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(
                 'pear.php.net',
                 array(