Ver código fonte

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

Conflicts:
	src/Composer/Repository/PearRepository.php
Bastian Hofmann 13 anos atrás
pai
commit
4b3fc2b5fa
64 arquivos alterados com 1277 adições e 412 exclusões
  1. 35 28
      CHANGELOG.md
  2. 3 3
      composer.lock
  3. 2 2
      doc/00-intro.md
  4. 6 6
      doc/01-basic-usage.md
  5. 15 6
      doc/02-libraries.md
  6. 15 5
      doc/03-cli.md
  7. 29 9
      doc/04-schema.md
  8. 63 12
      doc/05-repositories.md
  9. 90 0
      doc/articles/aliases.md
  10. 24 6
      doc/articles/handling-private-packages-with-satis.md
  11. 4 0
      res/composer-schema.json
  12. 105 38
      src/Composer/Autoload/AutoloadGenerator.php
  13. 2 1
      src/Composer/Command/CreateProjectCommand.php
  14. 1 1
      src/Composer/Command/InitCommand.php
  15. 1 1
      src/Composer/Command/SelfUpdateCommand.php
  16. 4 4
      src/Composer/Compiler.php
  17. 4 0
      src/Composer/Console/Application.php
  18. 38 2
      src/Composer/DependencyResolver/DefaultPolicy.php
  19. 4 3
      src/Composer/Downloader/DownloadManager.php
  20. 1 1
      src/Composer/Downloader/GitDownloader.php
  21. 27 2
      src/Composer/Factory.php
  22. 8 0
      src/Composer/IO/ConsoleIO.php
  23. 7 0
      src/Composer/IO/IOInterface.php
  24. 8 0
      src/Composer/IO/NullIO.php
  25. 40 11
      src/Composer/Installer.php
  26. 18 19
      src/Composer/Installer/InstallationManager.php
  27. 9 6
      src/Composer/Installer/LibraryInstaller.php
  28. 1 1
      src/Composer/Json/JsonFile.php
  29. 30 0
      src/Composer/Package/AliasPackage.php
  30. 9 1
      src/Composer/Package/Loader/ArrayLoader.php
  31. 29 2
      src/Composer/Package/Loader/RootPackageLoader.php
  32. 12 2
      src/Composer/Package/Locker.php
  33. 7 0
      src/Composer/Repository/ArrayRepository.php
  34. 2 2
      src/Composer/Repository/ComposerRepository.php
  35. 2 2
      src/Composer/Repository/PearRepository.php
  36. 3 9
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  37. 4 7
      src/Composer/Repository/Vcs/GitDriver.php
  38. 4 16
      src/Composer/Repository/Vcs/GitHubDriver.php
  39. 3 9
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  40. 8 11
      src/Composer/Repository/Vcs/HgDriver.php
  41. 11 19
      src/Composer/Repository/Vcs/SvnDriver.php
  42. 5 2
      src/Composer/Repository/Vcs/VcsDriver.php
  43. 50 48
      src/Composer/Repository/VcsRepository.php
  44. 34 6
      src/Composer/Util/StreamContextFactory.php
  45. 25 25
      src/bootstrap.php
  46. 75 53
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  47. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php
  48. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php
  49. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php
  50. 41 0
      tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
  51. 12 0
      tests/Composer/Test/Autoload/Fixtures/include_paths.php
  52. 30 0
      tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php
  53. 26 4
      tests/Composer/Test/Downloader/DownloadManagerTest.php
  54. 3 3
      tests/Composer/Test/Downloader/GitDownloaderTest.php
  55. 115 0
      tests/Composer/Test/InstallerTest.php
  56. 14 0
      tests/Composer/Test/Json/JsonFileTest.php
  57. 56 0
      tests/Composer/Test/Mock/InstallationManagerMock.php
  58. 26 0
      tests/Composer/Test/Mock/WritableRepositoryMock.php
  59. 1 1
      tests/Composer/Test/Package/Loader/ArrayLoaderTest.php
  60. 12 4
      tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php
  61. 3 2
      tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
  62. 2 1
      tests/Composer/Test/Repository/VcsRepositoryTest.php
  63. 9 6
      tests/Composer/Test/TestCase.php
  64. 47 7
      tests/Composer/Test/Util/StreamContextFactoryTest.php

+ 35 - 28
CHANGELOG.md

@@ -1,28 +1,35 @@
-* 1.0.0-alpha3
-
-  * Schema: Added 'require-dev' for development-time requirements (tests, etc), install with --dev
-  * Schema: Removed 'recommend'
-  * Schema: 'suggest' is now informational and can use any description for a package, not only a constraint
-  * Added caching of repository metadata (faster startup times & failover if packagist is down)
-  * Added include_path support for legacy projects that are full of require_once statements
-  * Added installation notifications API to allow better statistics on Composer repositories
-  * Improved repository protocol to have large cacheable parts
-
-* 1.0.0-alpha2 (2012-04-03)
-
-  * Added `create-project` command to install a project from scratch with composer
-  * Added automated `classmap` autoloading support for non-PSR-0 compliant projects
-  * Added human readable error reporting when deps can not be solved
-  * Added support for private GitHub and SVN repositories (use --no-interaction for CI)
-  * Added "file" downloader type to download plain files
-  * Added support for authentication with svn repositories
-  * Added autoload support for PEAR repositories
-  * Improved clones from GitHub which now automatically select between git/https/http protocols
-  * Improved `validate` command to give more feedback
-  * Improved the `search` & `show` commands output
-  * Removed dependency on filter_var
-  * Various robustness & error handling improvements, docs fixes and more bug fixes
-
-* 1.0.0-alpha1 (2012-03-01)
-
-  * Initial release
+* 1.0.0-alpha3
+
+  * Schema: Added 'require-dev' for development-time requirements (tests, etc), install with --dev
+  * Schema: Removed 'recommend'
+  * Schema: 'suggest' is now informational and can use any description for a package, not only a constraint
+  * Break: vendor/.composer/autoload.php has been moved to vendor/autoload.php, other files are now in vendor/composer/
+  * Added caching of repository metadata (faster startup times & failover if packagist is down)
+  * Added removal of packages that are not needed anymore
+  * Added include_path support for legacy projects that are full of require_once statements
+  * Added installation notifications API to allow better statistics on Composer repositories
+  * Added autoloading support for root packages that use target-dir
+  * Added awareness of the root package presence and support for it's provide/replace/conflict keys
+  * Added IOInterface::isDecorated to test for colored output support
+  * Improved repository protocol to have large cacheable parts
+  * Fixed various bugs relating to package aliasing, proxy configuration, binaries
+  * Various bug fixes and docs improvements
+
+* 1.0.0-alpha2 (2012-04-03)
+
+  * Added `create-project` command to install a project from scratch with composer
+  * Added automated `classmap` autoloading support for non-PSR-0 compliant projects
+  * Added human readable error reporting when deps can not be solved
+  * Added support for private GitHub and SVN repositories (use --no-interaction for CI)
+  * Added "file" downloader type to download plain files
+  * Added support for authentication with svn repositories
+  * Added autoload support for PEAR repositories
+  * Improved clones from GitHub which now automatically select between git/https/http protocols
+  * Improved `validate` command to give more feedback
+  * Improved the `search` & `show` commands output
+  * Removed dependency on filter_var
+  * Various robustness & error handling improvements, docs fixes and more bug fixes
+
+* 1.0.0-alpha1 (2012-03-01)
+
+  * Initial release

+ 3 - 3
composer.lock

@@ -12,19 +12,19 @@
         {
         {
             "package": "symfony/console",
             "package": "symfony/console",
             "version": "dev-master",
             "version": "dev-master",
-            "source-reference": "8e3c42aa976f18a9bfcb0694553e5f99def3309c",
+            "source-reference": "eaad4427b10ff39402bce0ae4f8cd1faf2b6532a",
             "alias": "2.1.9999999.9999999-dev"
             "alias": "2.1.9999999.9999999-dev"
         },
         },
         {
         {
             "package": "symfony/finder",
             "package": "symfony/finder",
             "version": "dev-master",
             "version": "dev-master",
-            "source-reference": "57ec7198a70e6c40e450ba66cc2f8ecab98746c8",
+            "source-reference": "78b2e33951821b6d423718f57788f1894dcb935a",
             "alias": "2.1.9999999.9999999-dev"
             "alias": "2.1.9999999.9999999-dev"
         },
         },
         {
         {
             "package": "symfony/process",
             "package": "symfony/process",
             "version": "dev-master",
             "version": "dev-master",
-            "source-reference": "2e4da8c8076744bafed97451bb1574c96cda0e68",
+            "source-reference": "718655f4bc664d693b33f3e6e8a895e454208021",
             "alias": "2.1.9999999.9999999-dev"
             "alias": "2.1.9999999.9999999-dev"
         }
         }
     ],
     ],

+ 2 - 2
doc/00-intro.md

@@ -67,7 +67,7 @@ executable and invoke it without `php`.
 
 
 ### Using Composer
 ### Using Composer
 
 
-Next, run the command the `install` command to resolve and download dependencies:
+Next, run the `install` command to resolve and download dependencies:
 
 
     $ php composer.phar install
     $ php composer.phar install
 
 
@@ -80,7 +80,7 @@ capable of autoloading all of the classes in any of the libraries that it
 downloads. To use it, just add the following line to your code's bootstrap
 downloads. To use it, just add the following line to your code's bootstrap
 process:
 process:
 
 
-    require 'vendor/.composer/autoload.php';
+    require 'vendor/autoload.php';
 
 
 Woh! Now start using monolog! To keep learning more about Composer, keep
 Woh! Now start using monolog! To keep learning more about Composer, keep
 reading the "Basic Usage" chapter.
 reading the "Basic Usage" chapter.

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

@@ -108,7 +108,7 @@ same version of the dependencies.
 If no `composer.json` lock file exists, it will read the dependencies and
 If no `composer.json` lock file exists, it will read the dependencies and
 versions from `composer.json` and  create the lock file.
 versions from `composer.json` and  create the lock file.
 
 
-This means that if any of the dependencies get a new version, you won't get the updates.
+This means that if any of the dependencies get a new version, you won't get the updates
 automatically. To update to the new version, use `update` command. This will fetch
 automatically. To update to the new version, use `update` command. This will fetch
 the latest matching versions (according to your `composer.json` file) and also update
 the latest matching versions (according to your `composer.json` file) and also update
 the lock file with the new version.
 the lock file with the new version.
@@ -136,10 +136,10 @@ but it makes life quite a bit simpler.
 ## Autoloading
 ## Autoloading
 
 
 For libraries that specify autoload information, Composer generates a
 For libraries that specify autoload information, Composer generates a
-`vendor/.composer/autoload.php` file. You can simply include this file and you
+`vendor/autoload.php` file. You can simply include this file and you
 will get autoloading for free.
 will get autoloading for free.
 
 
-    require 'vendor/.composer/autoload.php';
+    require 'vendor/autoload.php';
 
 
 This makes it really easy to use third party code. For example: If your
 This makes it really easy to use third party code. For example: If your
 project depends on monolog, you can just start using classes from it, and they
 project depends on monolog, you can just start using classes from it, and they
@@ -168,13 +168,13 @@ be in your project root. An example filename would be `src/Acme/Foo.php`
 containing an `Acme\Foo` class.
 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 `install` to re-generate
-the `vendor/.composer/autoload.php` file.
+the `vendor/autoload.php` file.
 
 
 Including that file will also return the autoloader instance, so you can store
 Including that file will also return the autoloader instance, so you can store
 the return value of the include call in a variable and add more namespaces.
 the return value of the include call in a variable and add more namespaces.
 This can be useful for autoloading classes in a test suite, for example.
 This can be useful for autoloading classes in a test suite, for example.
 
 
-    $loader = require 'vendor/.composer/autoload.php';
+    $loader = require 'vendor/autoload.php';
     $loader->add('Acme\Test', __DIR__);
     $loader->add('Acme\Test', __DIR__);
 
 
 In addition to PSR-0 autoloading, classmap is also supported. This allows
 In addition to PSR-0 autoloading, classmap is also supported. This allows
@@ -182,7 +182,7 @@ classes to be autoloaded even if they do not conform to PSR-0. See the
 [autoload reference](04-schema.md#autoload) for more details.
 [autoload reference](04-schema.md#autoload) for more details.
 
 
 > **Note:** Composer provides its own autoloader. If you don't want to use
 > **Note:** Composer provides its own autoloader. If you don't want to use
-that one, you can just include `vendor/.composer/autoload_namespaces.php`,
+that one, you can just include `vendor/autoload_namespaces.php`,
 which returns an associative array mapping namespaces to directories.
 which returns an associative array mapping namespaces to directories.
 
 
 ← [Intro](00-intro.md)  |  [Libraries](02-libraries.md) →
 ← [Intro](00-intro.md)  |  [Libraries](02-libraries.md) →

+ 15 - 6
doc/02-libraries.md

@@ -62,20 +62,29 @@ Here are a few examples of valid tag names:
 
 
 For every branch, a package development version will be created. If the branch
 For every branch, a package development version will be created. If the branch
 name looks like a version, the version will be `{branchname}-dev`. For example
 name looks like a version, the version will be `{branchname}-dev`. For example
-a branch `2.0` will get a version `2.0-dev`. If the branch does not look like
-a version, it will be `dev-{branchname}`. `master` results in a `dev-master`
-version.
+a branch `2.0` will get a version `2.0.x-dev` (the `.x` is added for technical
+reasons, to make sure it is recognized as a branch, a `2.0.x` branch would also
+be valid and be turned into `2.0.x-dev` as well. If the branch does not look
+like a version, it will be `dev-{branchname}`. `master` results in a 
+`dev-master` version.
 
 
 Here are some examples of version branch names:
 Here are some examples of version branch names:
 
 
-    1.0
-    1.*
+    1.x
+    1.0 (equals 1.0.x)
     1.1.x
     1.1.x
-    1.1.*
 
 
 > **Note:** When you install a dev version, it will install it from source.
 > **Note:** When you install a dev version, it will install it from source.
 See [Repositories](05-repositories.md) for more information.
 See [Repositories](05-repositories.md) for more information.
 
 
+### Aliases
+
+It is possible alias branch names to versions. For example, you could alias
+`dev-master` to `1.0.x-dev`, which would allow you to require `1.0.x-dev` in all
+the packages.
+
+See [Aliases](articles/aliases.md) for more information.
+
 ## Lock file
 ## Lock file
 
 
 For your library you may commit the `composer.lock` file if you want to. This
 For your library you may commit the `composer.lock` file if you want to. This

+ 15 - 5
doc/03-cli.md

@@ -199,11 +199,6 @@ directory other than `vendor`.
 By setting this option you can change the `bin` ([Vendor Bins](articles/vendor-bins.md))
 By setting this option you can change the `bin` ([Vendor Bins](articles/vendor-bins.md))
 directory to something other than `vendor/bin`.
 directory to something other than `vendor/bin`.
 
 
-### COMPOSER_PROCESS_TIMEOUT
-
-This env var controls the time composer waits for commands (such as git
-commands) to finish executing. The default value is 60 seconds.
-
 ### http_proxy or HTTP_PROXY
 ### http_proxy or HTTP_PROXY
 
 
 If you are using composer from behind an HTTP proxy, you can use the standard
 If you are using composer from behind an HTTP proxy, you can use the standard
@@ -215,4 +210,19 @@ some tools like git or curl will only use the lower-cased `http_proxy` version.
 Alternatively you can also define the git proxy using
 Alternatively you can also define the git proxy using
 `git config --global http.proxy <proxy url>`.
 `git config --global http.proxy <proxy url>`.
 
 
+### COMPOSER_HOME
+
+The `COMPOSER_HOME` var allows you to change the composer home directory. This
+is a hidden, global (per-user on the machine) directory that is shared between
+all projects.
+
+By default it points to `/home/<user>/.composer` on *nix,
+`/Users/<user>/.composer` on OSX and
+`C:\Users\<user>\AppData\Roaming\Composer` on Windows.
+
+### COMPOSER_PROCESS_TIMEOUT
+
+This env var controls the time composer waits for commands (such as git
+commands) to finish executing. The default value is 300 seconds (5 minutes).
+
 &larr; [Libraries](02-libraries.md)  |  [Schema](04-schema.md) &rarr;
 &larr; [Libraries](02-libraries.md)  |  [Schema](04-schema.md) &rarr;

+ 29 - 9
doc/04-schema.md

@@ -145,6 +145,7 @@ Each author object can have following properties:
 * **name:** The author's name. Usually his real name.
 * **name:** The author's name. Usually his real name.
 * **email:** The author's email address.
 * **email:** The author's email address.
 * **homepage:** An URL to the author's website.
 * **homepage:** An URL to the author's website.
+* **role:** The authors' role in the project (e.g. developer or translator)
 
 
 An example:
 An example:
 
 
@@ -153,12 +154,14 @@ An example:
             {
             {
                 "name": "Nils Adermann",
                 "name": "Nils Adermann",
                 "email": "naderman@naderman.de",
                 "email": "naderman@naderman.de",
-                "homepage": "http://www.naderman.de"
+                "homepage": "http://www.naderman.de",
+                "role": "Developer"
             },
             },
             {
             {
                 "name": "Jordi Boggiano",
                 "name": "Jordi Boggiano",
                 "email": "j.boggiano@seld.be",
                 "email": "j.boggiano@seld.be",
-                "homepage": "http://seld.be"
+                "homepage": "http://seld.be",
+                "role": "Developer"
             }
             }
         ]
         ]
     }
     }
@@ -215,21 +218,26 @@ Example:
 Autoload mapping for a PHP autoloader.
 Autoload mapping for a PHP autoloader.
 
 
 Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
 Currently [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
-autoloading and classmap generation are supported.
+autoloading and classmap generation are supported. PSR-0 is the recommended way though
+since it offers greater flexibility (no need to regenerate the autoloader when you add
+classes).
 
 
 Under the `psr-0` key you define a mapping from namespaces to paths, relative to the
 Under the `psr-0` key you define a mapping from namespaces to paths, relative to the
-package root.
+package root. Note that this also supports the PEAR-style convention.
 
 
 Example:
 Example:
 
 
     {
     {
         "autoload": {
         "autoload": {
-            "psr-0": { "Monolog": "src/" }
+            "psr-0": {
+                "Monolog": "src/",
+                "Vendor\\Namespace": "src/",
+                "Pear_Style": "src/"
+            }
         }
         }
     }
     }
 
 
-Optional, but it is highly recommended that you follow PSR-0 and use this.
-If you need to search for a same namespace prefix in multiple directories,
+If you need to search for a same prefix in multiple directories,
 you can specify them as an array as such:
 you can specify them as an array as such:
 
 
     {
     {
@@ -238,15 +246,24 @@ you can specify them as an array as such:
         }
         }
     }
     }
 
 
+If you want to have a fallback directory where any namespace can be, you can
+use an empty prefix like:
+
+    {
+        "autoload": {
+            "psr-0": { "": "src/" }
+        }
+    }
+
 You can use the classmap generation support to define autoloading for all libraries
 You can use the classmap generation support to define autoloading for all libraries
-that do not follow PSR-0. To configure this you specify all directories
+that do not follow PSR-0. To configure this you specify all directories or files
 to search for classes.
 to search for classes.
 
 
 Example:
 Example:
 
 
     {
     {
         "autoload: {
         "autoload: {
-            "classmap": ["src/", "lib/"]
+            "classmap": ["src/", "lib/", "Something.php"]
         }
         }
     }
     }
 
 
@@ -368,6 +385,9 @@ The following options are supported:
 * **process-timeout:** Defaults to `300`. The duration processes like git clones
 * **process-timeout:** Defaults to `300`. The duration processes like git clones
   can run before Composer assumes they died out. You may need to make this
   can run before Composer assumes they died out. You may need to make this
   higher if you have a slow connection or huge vendors.
   higher if you have a slow connection or huge vendors.
+* **notify-on-install:** Defaults to `true`. Composer allows repositories to
+  define a notification URL, so that they get notified whenever a package from
+  that repository is installed. This option allows you to disable that behaviour.
 
 
 Example:
 Example:
 
 

+ 63 - 12
doc/05-repositories.md

@@ -54,15 +54,24 @@ want to learn why.
 ### Composer
 ### Composer
 
 
 The main repository type is the `composer` repository. It uses a single
 The main repository type is the `composer` repository. It uses a single
-`packages.json` file that contains all of the package metadata. The JSON
-format is as follows:
+`packages.json` file that contains all of the package metadata.
+
+This is also the repository type that packagist uses. To reference a
+`composer` repository, just supply the path before the `packages.json` file.
+In case of packagist, that file is located at `/packages.json`, so the URL of
+the repository would be `packagist.org`. For `example.org/packages.json` the
+repository URL would be `example.org`.
+
+#### packages
+
+The only required field is `packages`. The JSON structure is as follows:
 
 
     {
     {
-        "vendor/packageName": {
-            "name": "vendor/packageName",
-            "description": "Package description",
-            "versions": {
+        "packages": {
+            "vendor/packageName": {
                 "master-dev": { @composer.json },
                 "master-dev": { @composer.json },
+                "1.0.x-dev": { @composer.json },
+                "0.0.1": { @composer.json },
                 "1.0.0": { @composer.json }
                 "1.0.0": { @composer.json }
             }
             }
         }
         }
@@ -88,12 +97,54 @@ Here is a minimal package definition:
 
 
 It may include any of the other fields specified in the [schema](04-schema.md).
 It may include any of the other fields specified in the [schema](04-schema.md).
 
 
-The `composer` repository is also what packagist uses. To reference a
-`composer` repository, just supply the path before the `packages.json` file.
-In case of packagist, that file is located at `/packages.json`, so the URL of
-the repository would be `http://packagist.org`. For
-`http://example.org/packages.json` the repository URL would be
-`http://example.org`.
+#### notify
+
+The `notify` field allows you to specify an URL template for a URL that will
+be called every time a user installs a package.
+
+An example value:
+
+    {
+        "notify": "/downloads/%package%"
+    }
+
+For `example.org/packages.json` containing a `monolog/monolog` package, this
+would send a `POST` request to `example.org/downloads/monolog/monolog` with
+following parameters:
+
+* **version:** The version of the package.
+* **version_normalized:** The normalized internal representation of the
+  version.
+
+This field is optional.
+
+#### includes
+
+For large repositories it is possible to split the `packages.json` into
+multiple files. The `includes` field allows you to reference these additional
+files.
+
+An example:
+
+    {
+        "includes": {
+            "packages-2011.json": {
+                "sha1": "525a85fb37edd1ad71040d429928c2c0edec9d17"
+            },
+            "packages-2012-01.json": {
+                "sha1": "897cde726f8a3918faf27c803b336da223d400dd"
+            },
+            "packages-2012-02.json": {
+                "sha1": "26f911ad717da26bbcac3f8f435280d13917efa5"
+            }
+        }
+    }
+
+The SHA-1 sum of the file allows it to be cached and only re-requested if the
+hash changed.
+
+This field is optional. You probably don't need it for your own custom
+repository.
 
 
 ### VCS
 ### VCS
 
 

+ 90 - 0
doc/articles/aliases.md

@@ -0,0 +1,90 @@
+<!--
+    tagline: Alias branch names to versions
+-->
+# Aliases
+
+## Why aliases?
+
+When you are using a VCS repository, you will only get comparable versions for
+branches that look like versions, such as `2.0`. For your `master` branch, you
+will get a `dev-master` version. For your `bugfix` branch, you will get a
+`dev-bugfix` version.
+
+If your `master` branch is used to tag releases of the `1.0` development line,
+i.e. `1.0.1`, `1.0.2`, `1.0.3`, etc., any package depending on it will
+probably require version `1.0.*`.
+
+If anyone wants to require the latest `dev-master`, they have a problem: Other
+packages may require `1.0.*`, so requiring that dev version will lead to
+conflicts, since `dev-master` does not match the `1.0.*` constraint.
+
+Enter aliases.
+
+## Branch alias
+
+The `dev-master` branch is one in your main VCS repo. It is rather common that
+someone will want the latest master dev version. Thus, Composer allows you to
+alias your `dev-master` branch to a `1.0.x-dev` version. It is done by
+specifying a `branch-alias` field under `extra` in `composer.json`:
+
+    {
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        }
+    }
+
+The branch version must begin with `dev-` (non-comparable version), the alias
+must be a comparable dev version. 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, you can now require `1.0.*` and it will happily install
+`dev-master` for you.
+
+## Require inline alias
+
+Branch aliases are great for aliasing main development lines. But in order to
+use them you need to have control over the source repository, and you need to
+commit changes to version control.
+
+This is not really fun when you just want to try a bugfix of some library that
+is a dependency of your local project.
+
+For this reason, you can alias packages in your `require` and `require-dev`
+fields. Let's say you found a bug in the `monolog/monolog` package. You cloned
+Monolog on GitHub and fixed the issue in a branch named `bugfix`. Now you want
+to install that version of monolog in your local project.
+
+You are using `symfony/monolog-bundle` which requires `monolog/monolog` version
+`1.*`. So you need your `dev-bugfix` to match that constraint.
+
+Just add this to your project's root `composer.json`:
+
+    {
+        "repositories": [
+            {
+                "type": "vcs",
+                "url": "https://github.com/you/monolog"
+            }
+        ],
+        "require": {
+            "symfony/monolog-bundle": "2.0",
+            "monolog/monolog": "dev-bugfix as 1.0.x-dev"
+        }
+    }
+
+That will fetch the `dev-bugfix` version of `monolog/monolog` from your GitHub
+and alias it to `1.0.x-dev`.
+
+> **Note:** If a package with inline aliases is required, the alias (right of
+> the `as`) is used as the version constraint. The part left of the `as` is
+> discarded. As a consequence, if A requires B and B requires `monolog/monolog`
+> version `dev-bugfix as 1.0.x-dev`, installing A will make B require
+> `1.0.x-dev`, which may exist as a branch alias or an actual `1.0` branch. If
+> it does not, it must be re-inline-aliased in A's `composer.json`.
+
+> **Note:** Inline aliasing should be avoided, especially for published
+> packages. If you found a bug, try and get your fix merged upstream. This
+> helps to avoid issues for users of your package.

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

@@ -5,7 +5,8 @@
 
 
 Satis can be used to host the metadata of your company's private packages, or
 Satis can be used to host the metadata of your company's private packages, or
 your own. It basically acts as a micro-packagist. You can get it from
 your own. It basically acts as a micro-packagist. You can get it from
-[GitHub](http://github.com/composer/satis).
+[GitHub](http://github.com/composer/satis) or install via CLI:
+`composer.phar create-project composer/satis`.
 
 
 ## Setup
 ## Setup
 
 
@@ -13,12 +14,29 @@ For example let's assume you have a few packages you want to reuse across your
 company but don't really want to open-source. You would first define a Satis
 company but don't really want to open-source. You would first define a Satis
 configuration file, which is basically a stripped-down version of a
 configuration file, which is basically a stripped-down version of a
 `composer.json` file. It contains a few repositories, and then you use the require
 `composer.json` file. It contains a few repositories, and then you use the require
-key to say which packages it should dump in the static repository it creates.
+key to say which packages it should dump in the static repository it creates, or
+use require-all to select all of them.
 
 
 Here is an example configuration, you see that it holds a few VCS repositories,
 Here is an example configuration, you see that it holds a few VCS repositories,
-but those could be any types of [repositories](../05-repositories.md). Then
-the require just lists all the packages we need, using a `"*"` constraint to
-make sure all versions are selected.
+but those could be any types of [repositories](../05-repositories.md). Then it
+uses `"require-all": true` which selects all versions of all packages in the
+repositories you defined.
+
+    {
+        "name": "My Repository",
+        "homepage": "http://packages.example.org",
+        "repositories": [
+            { "type": "vcs", "url": "http://github.com/mycompany/privaterepo" },
+            { "type": "vcs", "url": "http://svn.example.org/private/repo" },
+            { "type": "vcs", "url": "http://github.com/mycompany/privaterepo2" }
+        ],
+        "require-all": true
+    }
+
+If you want to cherry pick which packages you want, you can list all the packages
+you want to have in your satis repository inside the classic composer `require` key,
+using a `"*"` constraint to make sure all versions are selected, or another
+constraint if you want really specific versions.
 
 
     {
     {
         "repositories": [
         "repositories": [
@@ -29,7 +47,7 @@ make sure all versions are selected.
         "require": {
         "require": {
             "company/package": "*",
             "company/package": "*",
             "company/package2": "*",
             "company/package2": "*",
-            "company/package3": "*"
+            "company/package3": "2.0.0"
         }
         }
     }
     }
 
 

+ 4 - 0
res/composer-schema.json

@@ -66,6 +66,10 @@
                         "type": "string",
                         "type": "string",
                         "description": "Homepage URL for the author.",
                         "description": "Homepage URL for the author.",
                         "format": "uri"
                         "format": "uri"
+                    },
+                    "role": {
+                        "type": "string",
+                        "description": "Author's role in the project."
                     }
                     }
                 }
                 }
             }
             }

+ 105 - 38
src/Composer/Autoload/AutoloadGenerator.php

@@ -25,25 +25,26 @@ use Composer\Util\Filesystem;
  */
  */
 class AutoloadGenerator
 class AutoloadGenerator
 {
 {
-    public function dump(RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir)
+    public function dump(RepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $bcLinks = false)
     {
     {
         $filesystem = new Filesystem();
         $filesystem = new Filesystem();
         $filesystem->ensureDirectoryExists($installationManager->getVendorPath());
         $filesystem->ensureDirectoryExists($installationManager->getVendorPath());
         $filesystem->ensureDirectoryExists($targetDir);
         $filesystem->ensureDirectoryExists($targetDir);
         $vendorPath = strtr(realpath($installationManager->getVendorPath()), '\\', '/');
         $vendorPath = strtr(realpath($installationManager->getVendorPath()), '\\', '/');
         $relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath, true);
         $relVendorPath = $filesystem->findShortestPath(getcwd(), $vendorPath, true);
-        $vendorDirCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
+        $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
+        $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);
 
 
-        $appBaseDir = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
-        $appBaseDir = str_replace('__DIR__', '$vendorDir', $appBaseDir);
+        $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
+        $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);
 
 
         $namespacesFile = <<<EOF
         $namespacesFile = <<<EOF
 <?php
 <?php
 
 
 // autoload_namespace.php generated by Composer
 // autoload_namespace.php generated by Composer
 
 
-\$vendorDir = $vendorDirCode;
-\$baseDir = $appBaseDir;
+\$vendorDir = $vendorPathCode;
+\$baseDir = $appBaseDirCode;
 
 
 return array(
 return array(
 
 
@@ -55,22 +56,7 @@ EOF;
         foreach ($autoloads['psr-0'] as $namespace => $paths) {
         foreach ($autoloads['psr-0'] as $namespace => $paths) {
             $exportedPaths = array();
             $exportedPaths = array();
             foreach ($paths as $path) {
             foreach ($paths as $path) {
-                $path = strtr($path, '\\', '/');
-                $baseDir = '';
-                if (!$filesystem->isAbsolutePath($path)) {
-                    if (strpos($path, $relVendorPath) === 0) {
-                        // path starts with vendor dir
-                        $path = substr($path, strlen($relVendorPath));
-                        $baseDir = '$vendorDir . ';
-                    } else {
-                        $path = '/'.$path;
-                        $baseDir = '$baseDir . ';
-                    }
-                } elseif (strpos($path, $vendorPath) === 0) {
-                    $path = substr($path, strlen($vendorPath));
-                    $baseDir = '$vendorDir . ';
-                }
-                $exportedPaths[] = $baseDir.var_export($path, true);
+                $exportedPaths[] = $this->getPathCode($filesystem, $relVendorPath, $vendorPath, $path);
             }
             }
             $exportedPrefix = var_export($namespace, true);
             $exportedPrefix = var_export($namespace, true);
             $namespacesFile .= "    $exportedPrefix => ";
             $namespacesFile .= "    $exportedPrefix => ";
@@ -87,13 +73,44 @@ EOF;
 
 
 // autoload_classmap.php generated by Composer
 // autoload_classmap.php generated by Composer
 
 
-\$vendorDir = $vendorDirCode;
-\$baseDir = $appBaseDir;
+\$vendorDir = $vendorPathCode;
+\$baseDir = $appBaseDirCode;
 
 
 return array(
 return array(
 
 
 EOF;
 EOF;
 
 
+        // add custom psr-0 autoloading if the root package has a target dir
+        $targetDirLoader = null;
+        $mainAutoload = $mainPackage->getAutoload();
+        if ($mainPackage->getTargetDir() && $mainAutoload['psr-0']) {
+            $levels = count(explode('/', trim(strtr($mainPackage->getTargetDir(), '\\', '/'), '/')));
+            $prefixes = implode(', ', array_map(function ($prefix) {
+                return var_export($prefix, true);
+            }, array_keys($mainAutoload['psr-0'])));
+            $baseDirFromVendorDirCode = $filesystem->findShortestPathCode($vendorPath, getcwd(), true);
+
+            $targetDirLoader = <<<EOF
+    spl_autoload_register(function(\$class) {
+        \$dir = $baseDirFromVendorDirCode . '/';
+        \$prefixes = array($prefixes);
+        foreach (\$prefixes as \$prefix) {
+            if (0 !== strpos(\$class, \$prefix)) {
+                continue;
+            }
+            \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
+            if (!stream_resolve_include_path(\$path)) {
+                return false;
+            }
+            require_once \$path;
+            return true;
+        }
+    });
+
+
+EOF;
+        }
+
         // flatten array
         // flatten array
         $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
         $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
         foreach ($autoloads['classmap'] as $dir) {
         foreach ($autoloads['classmap'] as $dir) {
@@ -106,11 +123,23 @@ EOF;
 
 
         file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
         file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
         file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile);
         file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile);
-        if ($includePathFile = $this->getIncludePathsFile($packageMap)) {
+        if ($includePathFile = $this->getIncludePathsFile($packageMap, $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
             file_put_contents($targetDir.'/include_paths.php', $includePathFile);
             file_put_contents($targetDir.'/include_paths.php', $includePathFile);
         }
         }
-        file_put_contents($targetDir.'/autoload.php', $this->getAutoloadFile(true, true, (Boolean) $includePathFile));
+        file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, true, true, (Boolean) $includePathFile, $targetDirLoader));
         copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
         copy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
+
+        // TODO BC feature, add E_DEPRECATED in autoload.php on April 30th, remove after May 30th
+        if ($bcLinks) {
+            $filesystem->ensureDirectoryExists($vendorPath.'/.composer');
+            file_put_contents($vendorPath.'/.composer/autoload_namespaces.php', "<?php\n// Deprecated file, use the one in root of vendor dir\nreturn include dirname(__DIR__).'/composer/autoload_namespaces.php';\n");
+            file_put_contents($vendorPath.'/.composer/autoload_classmap.php', "<?php\n// Deprecated file, use the one in root of vendor dir\nreturn include dirname(__DIR__).'/composer/autoload_classmap.php';\n");
+            file_put_contents($vendorPath.'/.composer/autoload.php', "<?php\n// Deprecated file, use the one in root of vendor dir\nreturn include dirname(__DIR__).'/autoload.php';\n");
+            file_put_contents($vendorPath.'/.composer/ClassLoader.php', "<?php\n// Deprecated file, use the one in root of vendor dir\nreturn include dirname(__DIR__).'/composer/ClassLoader.php';\n");
+            if ($includePathFile) {
+                file_put_contents($vendorPath.'/.composer/include_paths.php', "<?php\n// Deprecated file, use the one in root of vendor dir\nreturn include dirname(__DIR__).'/composer/include_paths.php';\n");
+            }
+        }
     }
     }
 
 
     public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
     public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
@@ -186,7 +215,7 @@ EOF;
         return $loader;
         return $loader;
     }
     }
 
 
-    protected function getIncludePathsFile(array $packageMap)
+    protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode)
     {
     {
         $includePaths = array();
         $includePaths = array();
 
 
@@ -198,6 +227,7 @@ EOF;
             }
             }
 
 
             foreach ($package->getIncludePaths() as $includePath) {
             foreach ($package->getIncludePaths() as $includePath) {
+                $includePath = trim($includePath, '/');
                 $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath;
                 $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath;
             }
             }
         }
         }
@@ -206,30 +236,65 @@ EOF;
             return;
             return;
         }
         }
 
 
-        return sprintf(
-            "<?php\nreturn %s;\n", var_export($includePaths, true)
-        );
+        $includePathsFile = <<<EOF
+<?php
+
+// include_paths.php generated by Composer
+
+\$vendorDir = $vendorPathCode;
+\$baseDir = $appBaseDirCode;
+
+return array(
+
+EOF;
+
+        foreach ($includePaths as $path) {
+            $includePathsFile .= "    " . $this->getPathCode($filesystem, $relVendorPath, $vendorPath, $path) . ",\n";
+        }
+
+        return $includePathsFile . ");\n";
     }
     }
 
 
-    protected function getAutoloadFile($usePSR0, $useClassMap, $useIncludePath)
+    protected function getPathCode(Filesystem $filesystem, $relVendorPath, $vendorPath, $path)
     {
     {
-        $file = <<<'HEADER'
+        $path = strtr($path, '\\', '/');
+        $baseDir = '';
+        if (!$filesystem->isAbsolutePath($path)) {
+            if (strpos($path, $relVendorPath) === 0) {
+                // path starts with vendor dir
+                $path = substr($path, strlen($relVendorPath));
+                $baseDir = '$vendorDir . ';
+            } else {
+                $path = '/'.$path;
+                $baseDir = '$baseDir . ';
+            }
+        } elseif (strpos($path, $vendorPath) === 0) {
+            $path = substr($path, strlen($vendorPath));
+            $baseDir = '$vendorDir . ';
+        }
+        return $baseDir.var_export($path, true);
+    }
+
+    protected function getAutoloadFile($vendorPathToTargetDirCode, $usePSR0, $useClassMap, $useIncludePath, $targetDirLoader)
+    {
+        $file = <<<HEADER
 <?php
 <?php
 
 
 // autoload.php generated by Composer
 // autoload.php generated by Composer
-if (!class_exists('Composer\\Autoload\\ClassLoader', false)) {
-    require __DIR__.'/ClassLoader.php';
+if (!class_exists('Composer\\\\Autoload\\\\ClassLoader', false)) {
+    require $vendorPathToTargetDirCode . '/ClassLoader.php';
 }
 }
 
 
 return call_user_func(function() {
 return call_user_func(function() {
-    $loader = new \Composer\Autoload\ClassLoader();
+    \$loader = new \\Composer\\Autoload\\ClassLoader();
+    \$composerDir = $vendorPathToTargetDirCode;
 
 
 
 
 HEADER;
 HEADER;
 
 
         if ($useIncludePath) {
         if ($useIncludePath) {
             $file .= <<<'INCLUDE_PATH'
             $file .= <<<'INCLUDE_PATH'
-    $includePaths = require __DIR__.'/include_paths.php';
+    $includePaths = require $composerDir . '/include_paths.php';
     array_unshift($includePaths, get_include_path());
     array_unshift($includePaths, get_include_path());
     set_include_path(join(PATH_SEPARATOR, $includePaths));
     set_include_path(join(PATH_SEPARATOR, $includePaths));
 
 
@@ -239,7 +304,7 @@ INCLUDE_PATH;
 
 
         if ($usePSR0) {
         if ($usePSR0) {
             $file .= <<<'PSR0'
             $file .= <<<'PSR0'
-    $map = require __DIR__.'/autoload_namespaces.php';
+    $map = require $composerDir . '/autoload_namespaces.php';
     foreach ($map as $namespace => $path) {
     foreach ($map as $namespace => $path) {
         $loader->add($namespace, $path);
         $loader->add($namespace, $path);
     }
     }
@@ -250,7 +315,7 @@ PSR0;
 
 
         if ($useClassMap) {
         if ($useClassMap) {
             $file .= <<<'CLASSMAP'
             $file .= <<<'CLASSMAP'
-    $classMap = require __DIR__.'/autoload_classmap.php';
+    $classMap = require $composerDir . '/autoload_classmap.php';
     if ($classMap) {
     if ($classMap) {
         $loader->addClassMap($classMap);
         $loader->addClassMap($classMap);
     }
     }
@@ -259,6 +324,8 @@ PSR0;
 CLASSMAP;
 CLASSMAP;
         }
         }
 
 
+        $file .= $targetDirLoader;
+
         return $file . <<<'FOOTER'
         return $file . <<<'FOOTER'
     $loader->register();
     $loader->register();
 
 

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

@@ -18,6 +18,7 @@ use Composer\Installer\ProjectInstaller;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\Repository\ComposerRepository;
 use Composer\Repository\ComposerRepository;
 use Composer\Repository\FilesystemRepository;
 use Composer\Repository\FilesystemRepository;
+use Composer\Repository\InstalledFilesystemRepository;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Input\InputOption;
@@ -113,7 +114,7 @@ EOT
 
 
         $io->write('<info>Installing ' . $package->getName() . ' as new project.</info>', true);
         $io->write('<info>Installing ' . $package->getName() . ' as new project.</info>', true);
         $projectInstaller = new ProjectInstaller($directory, $dm);
         $projectInstaller = new ProjectInstaller($directory, $dm);
-        $projectInstaller->install(new FilesystemRepository(new JsonFile('php://memory')), $package);
+        $projectInstaller->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), $package);
 
 
         $io->write('<info>Created project into directory ' . $directory . '</info>', true);
         $io->write('<info>Created project into directory ' . $directory . '</info>', true);
         chdir($directory);
         chdir($directory);

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

@@ -35,7 +35,7 @@ class InitCommand extends Command
     public function parseAuthorString($author)
     public function parseAuthorString($author)
     {
     {
         if (preg_match('/^(?P<name>[- \.,\w\'’]+) <(?P<email>.+?)>$/u', $author, $match)) {
         if (preg_match('/^(?P<name>[- \.,\w\'’]+) <(?P<email>.+?)>$/u', $author, $match)) {
-            if (!function_exists('filter_var') || $match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) {
+            if (!function_exists('filter_var') || version_compare(PHP_VERSION, '5.3.3', '<') || $match['email'] === filter_var($match['email'], FILTER_VALIDATE_EMAIL)) {
                 return array(
                 return array(
                     'name'  => trim($match['name']),
                     'name'  => trim($match['name']),
                     'email' => $match['email']
                     'email' => $match['email']

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

@@ -53,7 +53,7 @@ EOT
             $rfs->copy('getcomposer.org', $remoteFilename, $tempFilename);
             $rfs->copy('getcomposer.org', $remoteFilename, $tempFilename);
 
 
             try {
             try {
-                chmod($tempFilename, 0755);
+                chmod($tempFilename, 0777 & ~umask());
                 // test the phar validity
                 // test the phar validity
                 $phar = new \Phar($tempFilename);
                 $phar = new \Phar($tempFilename);
                 // free the variable to unlock the file
                 // free the variable to unlock the file

+ 4 - 4
src/Composer/Compiler.php

@@ -81,10 +81,10 @@ class Compiler
             $this->addFile($phar, $file);
             $this->addFile($phar, $file);
         }
         }
 
 
-        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/ClassLoader.php'));
-        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload.php'));
-        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_namespaces.php'));
-        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/.composer/autoload_classmap.php'));
+        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php'));
+        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php'));
+        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php'));
+        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php'));
         $this->addComposerBin($phar);
         $this->addComposerBin($phar);
 
 
         // Stubs
         // Stubs

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

@@ -67,6 +67,10 @@ class Application extends BaseApplication
         $this->registerCommands();
         $this->registerCommands();
         $this->io = new ConsoleIO($input, $output, $this->getHelperSet());
         $this->io = new ConsoleIO($input, $output, $this->getHelperSet());
 
 
+        if (version_compare(PHP_VERSION, '5.3.2', '<')) {
+            $output->writeln('<warning>Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.</warning>');
+        }
+
         return parent::doRun($input, $output);
         return parent::doRun($input, $output);
     }
     }
 
 

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

@@ -69,6 +69,8 @@ class DefaultPolicy implements PolicyInterface
             $literals = $this->pruneToBestVersion($literals);
             $literals = $this->pruneToBestVersion($literals);
 
 
             $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
             $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
+
+            $literals = $this->pruneRemoteAliases($literals);
         }
         }
 
 
         $selected = call_user_func_array('array_merge', $packages);
         $selected = call_user_func_array('array_merge', $packages);
@@ -210,8 +212,8 @@ class DefaultPolicy implements PolicyInterface
     }
     }
 
 
     /**
     /**
-    * Assumes that installed packages come first and then all highest priority packages
-    */
+     * Assumes that installed packages come first and then all highest priority packages
+     */
     protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
     protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
     {
     {
         $selected = array();
         $selected = array();
@@ -239,4 +241,38 @@ class DefaultPolicy implements PolicyInterface
 
 
         return $selected;
         return $selected;
     }
     }
+
+    /**
+     * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
+     *
+     * If no package is a local alias, nothing happens
+     */
+    protected function pruneRemoteAliases(array $literals)
+    {
+        $hasLocalAlias = false;
+
+        foreach ($literals as $literal) {
+            $package = $literal->getPackage();
+
+            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
+                $hasLocalAlias = true;
+                break;
+            }
+        }
+
+        if (!$hasLocalAlias) {
+            return $literals;
+        }
+
+        $selected = array();
+        foreach ($literals as $literal) {
+            $package = $literal->getPackage();
+
+            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
+                $selected[] = $literal;
+            }
+        }
+
+        return $selected;
+    }
 }
 }

+ 4 - 3
src/Composer/Downloader/DownloadManager.php

@@ -24,6 +24,7 @@ use Composer\Util\Filesystem;
 class DownloadManager
 class DownloadManager
 {
 {
     private $preferSource = false;
     private $preferSource = false;
+    private $filesystem;
     private $downloaders  = array();
     private $downloaders  = array();
 
 
     /**
     /**
@@ -31,9 +32,10 @@ class DownloadManager
      *
      *
      * @param   Boolean $preferSource   prefer downloading from source
      * @param   Boolean $preferSource   prefer downloading from source
      */
      */
-    public function __construct($preferSource = false)
+    public function __construct($preferSource = false, Filesystem $filesystem = null)
     {
     {
         $this->preferSource = $preferSource;
         $this->preferSource = $preferSource;
+        $this->filesystem = $filesystem ?: new Filesystem();
     }
     }
 
 
     /**
     /**
@@ -135,8 +137,7 @@ class DownloadManager
             throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
             throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
         }
         }
 
 
-        $fs = new Filesystem();
-        $fs->ensureDirectoryExists($targetDir);
+        $this->filesystem->ensureDirectoryExists($targetDir);
 
 
         $downloader = $this->getDownloaderForInstalledPackage($package);
         $downloader = $this->getDownloaderForInstalledPackage($package);
         $downloader->download($package, $targetDir);
         $downloader->download($package, $targetDir);

+ 1 - 1
src/Composer/Downloader/GitDownloader.php

@@ -61,7 +61,7 @@ class GitDownloader extends VcsDownloader
      */
      */
     protected function enforceCleanDirectory($path)
     protected function enforceCleanDirectory($path)
     {
     {
-        $command = sprintf('cd %s && git status --porcelain', escapeshellarg($path));
+        $command = sprintf('cd %s && git status --porcelain --untracked-files=no', escapeshellarg($path));
         if (0 !== $this->process->execute($command, $output)) {
         if (0 !== $this->process->execute($command, $output)) {
             throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
             throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
         }
         }

+ 27 - 2
src/Composer/Factory.php

@@ -38,6 +38,14 @@ class Factory
             }
             }
         }
         }
 
 
+        // Protect directory against web access
+        if (!file_exists($home . '/.htaccess')) {
+            if (!is_dir($home)) {
+                @mkdir($home, 0777, true);
+            }
+            @file_put_contents($home . '/.htaccess', 'Deny from all');
+        }
+
         $config = new Config();
         $config = new Config();
 
 
         $file = new JsonFile($home.'/config.json');
         $file = new JsonFile($home.'/config.json');
@@ -151,8 +159,25 @@ class Factory
 
 
     protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
     protected function addLocalRepository(RepositoryManager $rm, $vendorDir)
     {
     {
-        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed.json')));
-        $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/.composer/installed_dev.json')));
+        // TODO BC feature, remove after May 30th
+        if (file_exists($vendorDir.'/.composer/installed.json')) {
+            if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); }
+            rename($vendorDir.'/.composer/installed.json', $vendorDir.'/composer/installed.json');
+        }
+        if (file_exists($vendorDir.'/.composer/installed_dev.json')) {
+            if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); }
+            rename($vendorDir.'/.composer/installed_dev.json', $vendorDir.'/composer/installed_dev.json');
+        }
+        if (file_exists($vendorDir.'/installed.json')) {
+            if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); }
+            rename($vendorDir.'/installed.json', $vendorDir.'/composer/installed.json');
+        }
+        if (file_exists($vendorDir.'/installed_dev.json')) {
+            if (!is_dir($vendorDir.'/composer')) { mkdir($vendorDir.'/composer/', 0777, true); }
+            rename($vendorDir.'/installed_dev.json', $vendorDir.'/composer/installed_dev.json');
+        }
+        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json')));
+        $rm->setLocalDevRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed_dev.json')));
     }
     }
 
 
     protected function addPackagistRepository(array $localConfig)
     protected function addPackagistRepository(array $localConfig)

+ 8 - 0
src/Composer/IO/ConsoleIO.php

@@ -55,6 +55,14 @@ class ConsoleIO implements IOInterface
         return $this->input->isInteractive();
         return $this->input->isInteractive();
     }
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
+    public function isDecorated()
+    {
+        return $this->output->isDecorated();
+    }
+
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */

+ 7 - 0
src/Composer/IO/IOInterface.php

@@ -33,6 +33,13 @@ interface IOInterface
      */
      */
     function isVerbose();
     function isVerbose();
 
 
+    /**
+     * Is this output decorated?
+     *
+     * @return Boolean
+     */
+    function isDecorated();
+
     /**
     /**
      * Writes a message to the output.
      * Writes a message to the output.
      *
      *

+ 8 - 0
src/Composer/IO/NullIO.php

@@ -35,6 +35,14 @@ class NullIO implements IOInterface
         return false;
         return false;
     }
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
+    public function isDecorated()
+    {
+        return false;
+    }
+
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */

+ 40 - 11
src/Composer/Installer.php

@@ -27,6 +27,7 @@ use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Locker;
 use Composer\Package\Locker;
 use Composer\Package\PackageInterface;
 use Composer\Package\PackageInterface;
+use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\RepositoryInterface;
@@ -76,6 +77,11 @@ class Installer
      */
      */
     protected $eventDispatcher;
     protected $eventDispatcher;
 
 
+    /**
+     * @var AutoloadGenerator
+     */
+    protected $autoloadGenerator;
+
     protected $preferSource = false;
     protected $preferSource = false;
     protected $devMode = false;
     protected $devMode = false;
     protected $dryRun = false;
     protected $dryRun = false;
@@ -102,8 +108,9 @@ class Installer
      * @param Locker $locker
      * @param Locker $locker
      * @param InstallationManager $installationManager
      * @param InstallationManager $installationManager
      * @param EventDispatcher $eventDispatcher
      * @param EventDispatcher $eventDispatcher
+     * @param AutoloadGenerator $autoloadGenerator
      */
      */
-    public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher)
+    public function __construct(IOInterface $io, PackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
     {
     {
         $this->io = $io;
         $this->io = $io;
         $this->package = $package;
         $this->package = $package;
@@ -112,6 +119,7 @@ class Installer
         $this->locker = $locker;
         $this->locker = $locker;
         $this->installationManager = $installationManager;
         $this->installationManager = $installationManager;
         $this->eventDispatcher = $eventDispatcher;
         $this->eventDispatcher = $eventDispatcher;
+        $this->autoloadGenerator = $autoloadGenerator;
     }
     }
 
 
     /**
     /**
@@ -128,7 +136,13 @@ class Installer
         }
         }
 
 
         // create installed repo, this contains all local packages + platform packages (php & extensions)
         // create installed repo, this contains all local packages + platform packages (php & extensions)
-        $repos = array_merge($this->repositoryManager->getLocalRepositories(), array(new PlatformRepository()));
+        $repos = array_merge(
+            $this->repositoryManager->getLocalRepositories(),
+            array(
+                new ArrayRepository(array($this->package)),
+                new PlatformRepository(),
+            )
+        );
         $installedRepo = new CompositeRepository($repos);
         $installedRepo = new CompositeRepository($repos);
         if ($this->additionalInstalledRepository) {
         if ($this->additionalInstalledRepository) {
             $installedRepo->addRepository($this->additionalInstalledRepository);
             $installedRepo->addRepository($this->additionalInstalledRepository);
@@ -152,9 +166,11 @@ class Installer
             }
             }
         }
         }
 
 
-        // dump suggestions
+        // output suggestions
         foreach ($this->suggestedPackages as $suggestion) {
         foreach ($this->suggestedPackages as $suggestion) {
-            $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
+            if (!$installedRepo->findPackages($suggestion['target'])) {
+                $this->io->write($suggestion['source'].' suggests installing '.$suggestion['target'].' ('.$suggestion['reason'].')');
+            }
         }
         }
 
 
         if (!$this->dryRun) {
         if (!$this->dryRun) {
@@ -172,9 +188,8 @@ class Installer
 
 
             // write autoloader
             // write autoloader
             $this->io->write('<info>Generating autoload files</info>');
             $this->io->write('<info>Generating autoload files</info>');
-            $generator = new AutoloadGenerator;
             $localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories());
             $localRepos = new CompositeRepository($this->repositoryManager->getLocalRepositories());
-            $generator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath().'/.composer');
+            $this->autoloadGenerator->dump($localRepos, $this->package, $this->installationManager, $this->installationManager->getVendorPath() . '/composer', true);
 
 
             // dispatch post event
             // dispatch post event
             $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
             $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
@@ -186,6 +201,11 @@ class Installer
 
 
     protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = false)
     protected function doInstall($localRepo, $installedRepo, $aliases, $devMode = false)
     {
     {
+        // initialize locker to create aliased packages
+        if (!$this->update && $this->locker->isLocked($devMode)) {
+            $lockedPackages = $this->locker->getLockedPackages($devMode);
+        }
+
         // creating repository pool
         // creating repository pool
         $pool = new Pool;
         $pool = new Pool;
         $pool->addRepository($installedRepo);
         $pool->addRepository($installedRepo);
@@ -196,6 +216,10 @@ class Installer
         // creating requirements request
         // creating requirements request
         $installFromLock = false;
         $installFromLock = false;
         $request = new Request($pool);
         $request = new Request($pool);
+
+        $constraint = new VersionConstraint('=', $this->package->getVersion());
+        $request->install($this->package->getName(), $constraint);
+
         if ($this->update) {
         if ($this->update) {
             $this->io->write('<info>Updating '.($devMode ? 'dev ': '').'dependencies</info>');
             $this->io->write('<info>Updating '.($devMode ? 'dev ': '').'dependencies</info>');
 
 
@@ -214,7 +238,7 @@ class Installer
                 $this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
                 $this->io->write('<warning>Your lock file is out of sync with your composer.json, run "composer.phar update" to update dependencies</warning>');
             }
             }
 
 
-            foreach ($this->locker->getLockedPackages($devMode) as $package) {
+            foreach ($lockedPackages as $package) {
                 $version = $package->getVersion();
                 $version = $package->getVersion();
                 foreach ($aliases as $alias) {
                 foreach ($aliases as $alias) {
                     if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
                     if ($alias['package'] === $package->getName() && $alias['version'] === $package->getVersion()) {
@@ -378,14 +402,16 @@ class Installer
             foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) {
             foreach ($this->repositoryManager->findPackages($alias['package'], $alias['version']) as $package) {
                 $package->setAlias($alias['alias_normalized']);
                 $package->setAlias($alias['alias_normalized']);
                 $package->setPrettyAlias($alias['alias']);
                 $package->setPrettyAlias($alias['alias']);
-                $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                $aliasPackage->setRootPackageAlias(true);
             }
             }
             foreach ($this->repositoryManager->getLocalRepositories() as $repo) {
             foreach ($this->repositoryManager->getLocalRepositories() as $repo) {
                 foreach ($repo->findPackages($alias['package'], $alias['version']) as $package) {
                 foreach ($repo->findPackages($alias['package'], $alias['version']) as $package) {
                     $package->setAlias($alias['alias_normalized']);
                     $package->setAlias($alias['alias_normalized']);
                     $package->setPrettyAlias($alias['alias']);
                     $package->setPrettyAlias($alias['alias']);
-                    $package->getRepository()->addPackage(new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
+                    $package->getRepository()->addPackage($aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']));
                     $package->getRepository()->removePackage($package);
                     $package->getRepository()->removePackage($package);
+                    $aliasPackage->setRootPackageAlias(true);
                 }
                 }
             }
             }
         }
         }
@@ -399,11 +425,13 @@ class Installer
      * @param IOInterface $io
      * @param IOInterface $io
      * @param Composer $composer
      * @param Composer $composer
      * @param EventDispatcher $eventDispatcher
      * @param EventDispatcher $eventDispatcher
+     * @param AutoloadGenerator $autoloadGenerator
      * @return Installer
      * @return Installer
      */
      */
-    static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null)
+    static public function create(IOInterface $io, Composer $composer, EventDispatcher $eventDispatcher = null, AutoloadGenerator $autoloadGenerator = null)
     {
     {
         $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io);
         $eventDispatcher = $eventDispatcher ?: new EventDispatcher($composer, $io);
+        $autoloadGenerator = $autoloadGenerator ?: new AutoloadGenerator;
 
 
         return new static(
         return new static(
             $io,
             $io,
@@ -412,7 +440,8 @@ class Installer
             $composer->getRepositoryManager(),
             $composer->getRepositoryManager(),
             $composer->getLocker(),
             $composer->getLocker(),
             $composer->getInstallationManager(),
             $composer->getInstallationManager(),
-            $eventDispatcher
+            $eventDispatcher,
+            $autoloadGenerator
         );
         );
     }
     }
 
 

+ 18 - 19
src/Composer/Installer/InstallationManager.php

@@ -127,11 +127,7 @@ class InstallationManager
      */
      */
     public function install(RepositoryInterface $repo, InstallOperation $operation)
     public function install(RepositoryInterface $repo, InstallOperation $operation)
     {
     {
-        $package = $operation->getPackage();
-        if ($package instanceof AliasPackage) {
-            $package = $package->getAliasOf();
-            $package->setInstalledAsAlias(true);
-        }
+        $package = $this->antiAlias($operation->getPackage());
         $installer = $this->getInstaller($package->getType());
         $installer = $this->getInstaller($package->getType());
         $installer->install($repo, $package);
         $installer->install($repo, $package);
         $this->notifyInstall($package);
         $this->notifyInstall($package);
@@ -145,15 +141,8 @@ class InstallationManager
      */
      */
     public function update(RepositoryInterface $repo, UpdateOperation $operation)
     public function update(RepositoryInterface $repo, UpdateOperation $operation)
     {
     {
-        $initial = $operation->getInitialPackage();
-        if ($initial instanceof AliasPackage) {
-            $initial = $initial->getAliasOf();
-        }
-        $target = $operation->getTargetPackage();
-        if ($target instanceof AliasPackage) {
-            $target = $target->getAliasOf();
-            $target->setInstalledAsAlias(true);
-        }
+        $initial = $this->antiAlias($operation->getInitialPackage());
+        $target = $this->antiAlias($operation->getTargetPackage());
 
 
         $initialType = $initial->getType();
         $initialType = $initial->getType();
         $targetType  = $target->getType();
         $targetType  = $target->getType();
@@ -176,10 +165,7 @@ class InstallationManager
      */
      */
     public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
     public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
     {
     {
-        $package = $operation->getPackage();
-        if ($package instanceof AliasPackage) {
-            $package = $package->getAliasOf();
-        }
+        $package = $this->antiAlias($operation->getPackage());
         $installer = $this->getInstaller($package->getType());
         $installer = $this->getInstaller($package->getType());
         $installer->uninstall($repo, $package);
         $installer->uninstall($repo, $package);
     }
     }
@@ -211,10 +197,23 @@ class InstallationManager
         return getcwd().DIRECTORY_SEPARATOR.$this->vendorPath;
         return getcwd().DIRECTORY_SEPARATOR.$this->vendorPath;
     }
     }
 
 
-    protected function notifyInstall(PackageInterface $package)
+    private function notifyInstall(PackageInterface $package)
     {
     {
         if ($package->getRepository() instanceof NotifiableRepositoryInterface) {
         if ($package->getRepository() instanceof NotifiableRepositoryInterface) {
             $package->getRepository()->notifyInstall($package);
             $package->getRepository()->notifyInstall($package);
         }
         }
     }
     }
+
+    private function antiAlias(PackageInterface $package)
+    {
+        if ($package instanceof AliasPackage) {
+            $alias = $package;
+            $package = $package->getAliasOf();
+            $package->setInstalledAsAlias(true);
+            $package->setAlias($alias->getVersion());
+            $package->setPrettyAlias($alias->getPrettyVersion());
+        }
+
+        return $package;
+    }
 }
 }

+ 9 - 6
src/Composer/Installer/LibraryInstaller.php

@@ -152,7 +152,7 @@ class LibraryInstaller implements InstallerInterface
                     // likely leftover from a previous install, make sure
                     // likely leftover from a previous install, make sure
                     // that the target is still executable in case this
                     // that the target is still executable in case this
                     // is a fresh install of the vendor.
                     // is a fresh install of the vendor.
-                    chmod($link, 0755);
+                    chmod($link, 0777 & ~umask());
                 }
                 }
                 $this->io->write('Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file');
                 $this->io->write('Skipped installation of '.$bin.' for package '.$package->getName().', name conflicts with an existing file');
                 continue;
                 continue;
@@ -163,21 +163,24 @@ class LibraryInstaller implements InstallerInterface
                 // add unixy support for cygwin and similar environments
                 // add unixy support for cygwin and similar environments
                 if ('.bat' !== substr($bin, -4)) {
                 if ('.bat' !== substr($bin, -4)) {
                     file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
                     file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
-                    chmod($link, 0755);
+                    chmod($link, 0777 & ~umask());
                     $link .= '.bat';
                     $link .= '.bat';
                 }
                 }
                 file_put_contents($link, $this->generateWindowsProxyCode($bin, $link));
                 file_put_contents($link, $this->generateWindowsProxyCode($bin, $link));
             } else {
             } else {
+                $cwd = getcwd();
                 try {
                 try {
                     // under linux symlinks are not always supported for example
                     // under linux symlinks are not always supported for example
                     // when using it in smbfs mounted folder
                     // when using it in smbfs mounted folder
-                    symlink($bin, $link);
+                    $relativeBin = $this->filesystem->findShortestPath($link, $bin);
+                    chdir(dirname($link));
+                    symlink($relativeBin, $link);
                 } catch (\ErrorException $e) {
                 } catch (\ErrorException $e) {
                     file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
                     file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
                 }
                 }
-
+                chdir($cwd);
             }
             }
-            chmod($link, 0755);
+            chmod($link, 0777 & ~umask());
         }
         }
     }
     }
 
 
@@ -186,7 +189,7 @@ class LibraryInstaller implements InstallerInterface
         if (!$package->getBinaries()) {
         if (!$package->getBinaries()) {
             return;
             return;
         }
         }
-        foreach ($package->getBinaries() as $bin => $os) {
+        foreach ($package->getBinaries() as $bin) {
             $link = $this->binDir.'/'.basename($bin);
             $link = $this->binDir.'/'.basename($bin);
             if (!file_exists($link)) {
             if (!file_exists($link)) {
                 continue;
                 continue;

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

@@ -237,7 +237,7 @@ class JsonFile
                     }
                     }
                 } else {
                 } else {
                     // Collapse empty {} and []
                     // Collapse empty {} and []
-                    $result = rtrim($result);
+                    $result = rtrim($result)."\n\n".$indentStr;
                 }
                 }
             }
             }
 
 

+ 30 - 0
src/Composer/Package/AliasPackage.php

@@ -27,6 +27,7 @@ class AliasPackage extends BasePackage
     protected $prettyVersion;
     protected $prettyVersion;
     protected $dev;
     protected $dev;
     protected $aliasOf;
     protected $aliasOf;
+    protected $rootPackageAlias = false;
 
 
     protected $requires;
     protected $requires;
     protected $conflicts;
     protected $conflicts;
@@ -146,6 +147,27 @@ class AliasPackage extends BasePackage
         return $this->devRequires;
         return $this->devRequires;
     }
     }
 
 
+    /**
+     * Stores whether this is an alias created by an aliasing in the requirements of the root package or not
+     *
+     * Use by the policy for sorting manually aliased packages first, see #576
+     *
+     * @param Boolean $value
+     */
+    public function setRootPackageAlias($value)
+    {
+        return $this->rootPackageAlias = $value;
+    }
+
+    /**
+     * @see setRootPackageAlias
+     * @return Boolean
+     */
+    public function isRootPackageAlias()
+    {
+        return $this->rootPackageAlias;
+    }
+
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
@@ -222,6 +244,14 @@ class AliasPackage extends BasePackage
     {
     {
         return $this->aliasOf->getScripts();
         return $this->aliasOf->getScripts();
     }
     }
+    public function setAliases(array $aliases)
+    {
+        return $this->aliasOf->setAliases($aliases);
+    }
+    public function getAliases()
+    {
+        return $this->aliasOf->getAliases();
+    }
     public function getLicense()
     public function getLicense()
     {
     {
         return $this->aliasOf->getLicense();
         return $this->aliasOf->getLicense();

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

@@ -58,7 +58,10 @@ class ArrayLoader
             $package->setExtra($config['extra']);
             $package->setExtra($config['extra']);
         }
         }
 
 
-        if (isset($config['bin']) && is_array($config['bin'])) {
+        if (isset($config['bin'])) {
+            if (!is_array($config['bin'])) {
+                throw new \UnexpectedValueException('Package '.$config['name'].'\'s bin key should be an array, '.gettype($config['bin']).' given.');
+            }
             foreach ($config['bin'] as $key => $bin) {
             foreach ($config['bin'] as $key => $bin) {
                 $config['bin'][$key]= ltrim($bin, '/');
                 $config['bin'][$key]= ltrim($bin, '/');
             }
             }
@@ -166,6 +169,11 @@ class ArrayLoader
         }
         }
 
 
         if (isset($config['suggest']) && is_array($config['suggest'])) {
         if (isset($config['suggest']) && is_array($config['suggest'])) {
+            foreach ($config['suggest'] as $target => $reason) {
+                if ('self.version' === trim($reason)) {
+                    $config['suggest'][$target] = $package->getPrettyVersion();
+                }
+            }
             $package->setSuggests($config['suggest']);
             $package->setSuggests($config['suggest']);
         }
         }
 
 

+ 29 - 2
src/Composer/Package/Loader/RootPackageLoader.php

@@ -14,6 +14,8 @@ namespace Composer\Package\Loader;
 
 
 use Composer\Package\Version\VersionParser;
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryManager;
 use Composer\Repository\RepositoryManager;
+use Composer\Util\ProcessExecutor;
+use Composer\Package\AliasPackage;
 
 
 /**
 /**
  * ArrayLoader built for the sole purpose of loading the root package
  * ArrayLoader built for the sole purpose of loading the root package
@@ -25,10 +27,12 @@ use Composer\Repository\RepositoryManager;
 class RootPackageLoader extends ArrayLoader
 class RootPackageLoader extends ArrayLoader
 {
 {
     private $manager;
     private $manager;
+    private $process;
 
 
-    public function __construct(RepositoryManager $manager, VersionParser $parser = null)
+    public function __construct(RepositoryManager $manager, VersionParser $parser = null, ProcessExecutor $process = null)
     {
     {
         $this->manager = $manager;
         $this->manager = $manager;
+        $this->process = $process ?: new ProcessExecutor();
         parent::__construct($parser);
         parent::__construct($parser);
     }
     }
 
 
@@ -38,7 +42,20 @@ class RootPackageLoader extends ArrayLoader
             $config['name'] = '__root__';
             $config['name'] = '__root__';
         }
         }
         if (!isset($config['version'])) {
         if (!isset($config['version'])) {
-            $config['version'] = '1.0.0';
+            $version = '1.0.0';
+
+            // try to fetch current version from git branch
+            if (0 === $this->process->execute('git branch --no-color --no-abbrev -v', $output)) {
+                foreach ($this->process->splitLines($output) as $branch) {
+                    if ($branch && preg_match('{^(?:\* ) *(?:[^/ ]+?/)?(\S+) *[a-f0-9]+ .*$}', $branch, $match)) {
+                        $version = 'dev-'.$match[1];
+                    }
+                }
+            }
+
+            $config['version'] = $version;
+        } else {
+            $version = $config['version'];
         }
         }
 
 
         $package = parent::load($config);
         $package = parent::load($config);
@@ -76,6 +93,16 @@ class RootPackageLoader extends ArrayLoader
             $package->setRepositories($config['repositories']);
             $package->setRepositories($config['repositories']);
         }
         }
 
 
+        if (isset($config['extra']['branch-alias'][$version])
+            && substr($config['extra']['branch-alias'][$version], -4) === '-dev'
+        ) {
+            $targetBranch = $config['extra']['branch-alias'][$version];
+            $normalized = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
+            $version = preg_replace('{(\.9{7})+}', '.x', $normalized);
+
+            return new AliasPackage($package, $normalized, $version);
+        }
+
         return $package;
         return $package;
     }
     }
 }
 }

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

@@ -90,12 +90,22 @@ class Locker
 
 
         foreach ($lockedPackages as $info) {
         foreach ($lockedPackages as $info) {
             $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version'];
             $resolvedVersion = !empty($info['alias']) ? $info['alias'] : $info['version'];
+
+            // try to find the package in the local repo (best match)
             $package = $repo->findPackage($info['package'], $resolvedVersion);
             $package = $repo->findPackage($info['package'], $resolvedVersion);
 
 
+            // try to find the package in any repo
             if (!$package) {
             if (!$package) {
+                $package = $this->repositoryManager->findPackage($info['package'], $resolvedVersion);
+            }
+
+            // try to find the package in any repo (second pass without alias + rebuild alias since it disappeared)
+            if (!$package && !empty($info['alias'])) {
                 $package = $this->repositoryManager->findPackage($info['package'], $info['version']);
                 $package = $this->repositoryManager->findPackage($info['package'], $info['version']);
-                if ($package && !empty($info['alias'])) {
-                    $package = new AliasPackage($package, $info['alias'], $info['alias']);
+                if ($package) {
+                    $alias = new AliasPackage($package, $info['alias'], $info['alias']);
+                    $package->getRepository()->addPackage($alias);
+                    $package = $alias;
                 }
                 }
             }
             }
 
 

+ 7 - 0
src/Composer/Repository/ArrayRepository.php

@@ -25,6 +25,13 @@ class ArrayRepository implements RepositoryInterface
 {
 {
     protected $packages;
     protected $packages;
 
 
+    public function __construct(array $packages = array())
+    {
+        foreach ($packages as $package) {
+            $this->addPackage($package);
+        }
+    }
+
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */

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

@@ -40,7 +40,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
             $repoConfig['url'] = 'http://'.$repoConfig['url'];
             $repoConfig['url'] = 'http://'.$repoConfig['url'];
         }
         }
         $repoConfig['url'] = rtrim($repoConfig['url'], '/');
         $repoConfig['url'] = rtrim($repoConfig['url'], '/');
-        if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) {
+        if (function_exists('filter_var') && version_compare(PHP_VERSION, '5.3.3', '>=') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) {
             throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
             throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
         }
         }
 
 
@@ -70,7 +70,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
             array(
             array(
                 'method'  => 'POST',
                 'method'  => 'POST',
                 'header'  => 'Content-type: application/x-www-form-urlencoded',
                 'header'  => 'Content-type: application/x-www-form-urlencoded',
-                'content' => http_build_query($params),
+                'content' => http_build_query($params, '', '&'),
                 'timeout' => 3,
                 'timeout' => 3,
             )
             )
         );
         );

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

@@ -39,7 +39,7 @@ class PearRepository extends ArrayRepository
             $repoConfig['url'] = 'http://'.$repoConfig['url'];
             $repoConfig['url'] = 'http://'.$repoConfig['url'];
         }
         }
 
 
-        if (function_exists('filter_var') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) {
+        if (function_exists('filter_var') && version_compare(PHP_VERSION, '5.3.3', '>=') && !filter_var($repoConfig['url'], FILTER_VALIDATE_URL)) {
             throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']);
             throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']);
         }
         }
 
 
@@ -74,7 +74,7 @@ class PearRepository extends ArrayRepository
                     }
                     }
                     $this->addPackage($loader->load($rev));
                     $this->addPackage($loader->load($rev));
                     if ($this->io->isVerbose()) {
                     if ($this->io->isVerbose()) {
-                        $this->io->write('Loaded '. $data['name'].' '. $data['version']);
+                        $this->io->write('Loaded '.$rev['name'].' '.$rev['version']);
                     }
                     }
                 }
                 }
             }
             }

+ 3 - 9
src/Composer/Repository/Vcs/GitBitbucketDriver.php

@@ -27,20 +27,14 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     protected $rootIdentifier;
     protected $rootIdentifier;
     protected $infoCache = array();
     protected $infoCache = array();
 
 
-    public function __construct($url, IOInterface $io)
-    {
-        preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $url, $match);
-        $this->owner = $match[1];
-        $this->repository = $match[2];
-
-        parent::__construct($url, $io);
-    }
-
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
     public function initialize()
     public function initialize()
     {
     {
+        preg_match('#^https://bitbucket\.org/([^/]+)/(.+?)\.git$#', $this->url, $match);
+        $this->owner = $match[1];
+        $this->repository = $match[2];
     }
     }
 
 
     /**
     /**

+ 4 - 7
src/Composer/Repository/Vcs/GitDriver.php

@@ -28,11 +28,6 @@ class GitDriver extends VcsDriver
     protected $repoDir;
     protected $repoDir;
     protected $infoCache = array();
     protected $infoCache = array();
 
 
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
-    {
-        parent::__construct($url, $io, $process);
-    }
-
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
@@ -41,11 +36,13 @@ class GitDriver extends VcsDriver
         if (static::isLocalUrl($this->url)) {
         if (static::isLocalUrl($this->url)) {
             $this->repoDir = str_replace('file://', '', $this->url);
             $this->repoDir = str_replace('file://', '', $this->url);
         } else {
         } else {
-            $this->repoDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/';
+            $this->repoDir = $this->config->get('home') . '/cache.git/' . preg_replace('{[^a-z0-9.]}i', '-', $this->url) . '/';
 
 
             // update the repo if it is a valid git repository
             // update the repo if it is a valid git repository
             if (is_dir($this->repoDir) && 0 === $this->process->execute('git remote', $output, $this->repoDir)) {
             if (is_dir($this->repoDir) && 0 === $this->process->execute('git remote', $output, $this->repoDir)) {
-                $this->process->execute('git remote update --prune origin', $output, $this->repoDir);
+                if (0 !== $this->process->execute('git remote update --prune origin', $output, $this->repoDir)) {
+                    $this->io->write('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
+                }
             } else {
             } else {
                 // clean up directory and do a fresh clone into it
                 // clean up directory and do a fresh clone into it
                 $fs = new Filesystem();
                 $fs = new Filesystem();

+ 4 - 16
src/Composer/Repository/Vcs/GitHubDriver.php

@@ -39,27 +39,14 @@ class GitHubDriver extends VcsDriver
     protected $gitDriver;
     protected $gitDriver;
 
 
     /**
     /**
-     * Constructor
-     *
-     * @param string $url
-     * @param IOInterface $io
-     * @param ProcessExecutor $process
-     * @param RemoteFilesystem $remoteFilesystem
+     * {@inheritDoc}
      */
      */
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
+    public function initialize()
     {
     {
-        preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $url, $match);
+        preg_match('#^(?:https?|git)://github\.com/([^/]+)/(.+?)(?:\.git)?$#', $this->url, $match);
         $this->owner = $match[1];
         $this->owner = $match[1];
         $this->repository = $match[2];
         $this->repository = $match[2];
 
 
-        parent::__construct($url, $io, $process, $remoteFilesystem);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function initialize()
-    {
         $this->fetchRootIdentifier();
         $this->fetchRootIdentifier();
     }
     }
 
 
@@ -248,6 +235,7 @@ class GitHubDriver extends VcsDriver
                             $this->gitDriver = new GitDriver(
                             $this->gitDriver = new GitDriver(
                                 $this->generateSshUrl(),
                                 $this->generateSshUrl(),
                                 $this->io,
                                 $this->io,
+                                $this->config,
                                 $this->process,
                                 $this->process,
                                 $this->remoteFilesystem
                                 $this->remoteFilesystem
                             );
                             );

+ 3 - 9
src/Composer/Repository/Vcs/HgBitbucketDriver.php

@@ -27,20 +27,14 @@ class HgBitbucketDriver extends VcsDriver
     protected $rootIdentifier;
     protected $rootIdentifier;
     protected $infoCache = array();
     protected $infoCache = array();
 
 
-    public function __construct($url, IOInterface $io)
-    {
-        preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $url, $match);
-        $this->owner = $match[1];
-        $this->repository = $match[2];
-
-        parent::__construct($url, $io);
-    }
-
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
     public function initialize()
     public function initialize()
     {
     {
+        preg_match('#^https://bitbucket\.org/([^/]+)/([^/]+)/?$#', $this->url, $match);
+        $this->owner = $match[1];
+        $this->repository = $match[2];
     }
     }
 
 
     /**
     /**

+ 8 - 11
src/Composer/Repository/Vcs/HgDriver.php

@@ -26,24 +26,21 @@ class HgDriver extends VcsDriver
     protected $rootIdentifier;
     protected $rootIdentifier;
     protected $infoCache = array();
     protected $infoCache = array();
 
 
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
-    {
-        $this->tmpDir = sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9]}i', '-', $url) . '/';
-
-        parent::__construct($url, $io, $process);
-    }
-
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
     public function initialize()
     public function initialize()
     {
     {
-        $url = escapeshellarg($this->url);
-        $tmpDir = escapeshellarg($this->tmpDir);
+        $this->tmpDir = $this->config->get('home') . '/cache.hg/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/';
+
         if (is_dir($this->tmpDir)) {
         if (is_dir($this->tmpDir)) {
-            $this->process->execute(sprintf('cd %s && hg pull -u', $tmpDir), $output);
+            $this->process->execute(sprintf('cd %s && hg pull -u', escapeshellarg($this->tmpDir)), $output);
         } else {
         } else {
-            $this->process->execute(sprintf('cd %s && hg clone %s %s', escapeshellarg(sys_get_temp_dir()), $url, $tmpDir), $output);
+            $dir = dirname($this->tmpDir);
+            if (!is_dir($dir)) {
+                mkdir($dir, 0777, true);
+            }
+            $this->process->execute(sprintf('cd %s && hg clone %s %s', escapeshellarg($dir), escapeshellarg($this->url), escapeshellarg($this->tmpDir)), $output);
         }
         }
 
 
         $this->getTags();
         $this->getTags();

+ 11 - 19
src/Composer/Repository/Vcs/SvnDriver.php

@@ -33,25 +33,7 @@ class SvnDriver extends VcsDriver
     /**
     /**
      * @var \Composer\Util\Svn
      * @var \Composer\Util\Svn
      */
      */
-    protected $util;
-
-    /**
-     * @param string          $url
-     * @param IOInterface     $io
-     * @param ProcessExecutor $process
-     *
-     * @return $this
-     */
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
-    {
-        $url = self::normalizeUrl($url);
-        parent::__construct($this->baseUrl = rtrim($url, '/'), $io, $process);
-
-        if (false !== ($pos = strrpos($url, '/trunk'))) {
-            $this->baseUrl = substr($url, 0, $pos);
-        }
-        $this->util    = new SvnUtil($this->baseUrl, $io, $this->process);
-    }
+    private $util;
 
 
     /**
     /**
      * Execute an SVN command and try to fix up the process with credentials
      * Execute an SVN command and try to fix up the process with credentials
@@ -64,6 +46,10 @@ class SvnDriver extends VcsDriver
      */
      */
     protected function execute($command, $url)
     protected function execute($command, $url)
     {
     {
+        if (null === $this->util) {
+            $this->util = new SvnUtil($this->baseUrl, $this->io, $this->process);
+        }
+
         try {
         try {
             return $this->util->execute($command, $url);
             return $this->util->execute($command, $url);
         } catch (\RuntimeException $e) {
         } catch (\RuntimeException $e) {
@@ -78,6 +64,12 @@ class SvnDriver extends VcsDriver
      */
      */
     public function initialize()
     public function initialize()
     {
     {
+        $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/');
+
+        if (false !== ($pos = strrpos($this->url, '/trunk'))) {
+            $this->baseUrl = substr($this->url, 0, $pos);
+        }
+
         $this->getBranches();
         $this->getBranches();
         $this->getTags();
         $this->getTags();
     }
     }

+ 5 - 2
src/Composer/Repository/Vcs/VcsDriver.php

@@ -13,6 +13,7 @@
 namespace Composer\Repository\Vcs;
 namespace Composer\Repository\Vcs;
 
 
 use Composer\Downloader\TransportException;
 use Composer\Downloader\TransportException;
+use Composer\Config;
 use Composer\IO\IOInterface;
 use Composer\IO\IOInterface;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\RemoteFilesystem;
 use Composer\Util\RemoteFilesystem;
@@ -26,6 +27,7 @@ abstract class VcsDriver implements VcsDriverInterface
 {
 {
     protected $url;
     protected $url;
     protected $io;
     protected $io;
+    protected $config;
     protected $process;
     protected $process;
     protected $remoteFilesystem;
     protected $remoteFilesystem;
 
 
@@ -34,13 +36,15 @@ abstract class VcsDriver implements VcsDriverInterface
      *
      *
      * @param string      $url The URL
      * @param string      $url The URL
      * @param IOInterface $io  The IO instance
      * @param IOInterface $io  The IO instance
+     * @param Config      $config The composer configuration
      * @param ProcessExecutor $process  Process instance, injectable for mocking
      * @param ProcessExecutor $process  Process instance, injectable for mocking
      * @param callable $remoteFilesystem Remote Filesystem, injectable for mocking
      * @param callable $remoteFilesystem Remote Filesystem, injectable for mocking
      */
      */
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null, $remoteFilesystem = null)
+    final public function __construct($url, IOInterface $io, Config $config, ProcessExecutor $process = null, $remoteFilesystem = null)
     {
     {
         $this->url = $url;
         $this->url = $url;
         $this->io = $io;
         $this->io = $io;
+        $this->config = $config;
         $this->process = $process ?: new ProcessExecutor;
         $this->process = $process ?: new ProcessExecutor;
         $this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io);
         $this->remoteFilesystem = $remoteFilesystem ?: new RemoteFilesystem($io);
     }
     }
@@ -58,7 +62,6 @@ abstract class VcsDriver implements VcsDriverInterface
         return false;
         return false;
     }
     }
 
 
-
     /**
     /**
      * Get the https or http protocol depending on SSL support.
      * Get the https or http protocol depending on SSL support.
      *
      *

+ 50 - 48
src/Composer/Repository/VcsRepository.php

@@ -30,10 +30,11 @@ class VcsRepository extends ArrayRepository
     protected $packageName;
     protected $packageName;
     protected $verbose;
     protected $verbose;
     protected $io;
     protected $io;
+    protected $config;
     protected $versionParser;
     protected $versionParser;
     protected $type;
     protected $type;
 
 
-    public function __construct(array $repoConfig, IOInterface $io, Config $config = null, array $drivers = null)
+    public function __construct(array $repoConfig, IOInterface $io, Config $config, array $drivers = null)
     {
     {
         $this->drivers = $drivers ?: array(
         $this->drivers = $drivers ?: array(
             'github'        => 'Composer\Repository\Vcs\GitHubDriver',
             'github'        => 'Composer\Repository\Vcs\GitHubDriver',
@@ -48,20 +49,21 @@ class VcsRepository extends ArrayRepository
         $this->io = $io;
         $this->io = $io;
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
         $this->verbose = $io->isVerbose();
         $this->verbose = $io->isVerbose();
+        $this->config = $config;
     }
     }
 
 
     public function getDriver()
     public function getDriver()
     {
     {
         if (isset($this->drivers[$this->type])) {
         if (isset($this->drivers[$this->type])) {
             $class = $this->drivers[$this->type];
             $class = $this->drivers[$this->type];
-            $driver = new $class($this->url, $this->io);
+            $driver = new $class($this->url, $this->io, $this->config);
             $driver->initialize();
             $driver->initialize();
             return $driver;
             return $driver;
         }
         }
 
 
         foreach ($this->drivers as $driver) {
         foreach ($this->drivers as $driver) {
             if ($driver::supports($this->io, $this->url)) {
             if ($driver::supports($this->io, $this->url)) {
-                $driver = new $driver($this->url, $this->io);
+                $driver = new $driver($this->url, $this->io, $this->config);
                 $driver->initialize();
                 $driver->initialize();
                 return $driver;
                 return $driver;
             }
             }
@@ -69,7 +71,7 @@ class VcsRepository extends ArrayRepository
 
 
         foreach ($this->drivers as $driver) {
         foreach ($this->drivers as $driver) {
             if ($driver::supports($this->io, $this->url, true)) {
             if ($driver::supports($this->io, $this->url, true)) {
-                $driver = new $driver($this->url, $this->io);
+                $driver = new $driver($this->url, $this->io, $this->config);
                 $driver->initialize();
                 $driver->initialize();
                 return $driver;
                 return $driver;
             }
             }
@@ -102,7 +104,7 @@ class VcsRepository extends ArrayRepository
         }
         }
 
 
         foreach ($driver->getTags() as $tag => $identifier) {
         foreach ($driver->getTags() as $tag => $identifier) {
-            $msg = 'Get composer info for <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
+            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';
             if ($verbose) {
             if ($verbose) {
                 $this->io->write($msg);
                 $this->io->write($msg);
             } else {
             } else {
@@ -123,45 +125,45 @@ class VcsRepository extends ArrayRepository
                     }
                     }
                     continue;
                     continue;
                 }
                 }
-            } catch (\Exception $e) {
-                if ($verbose) {
-                    $this->io->write('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()));
+
+                // manually versioned package
+                if (isset($data['version'])) {
+                    $data['version_normalized'] = $this->versionParser->normalize($data['version']);
+                } else {
+                    // auto-versionned package, read value from tag
+                    $data['version'] = $tag;
+                    $data['version_normalized'] = $parsedTag;
                 }
                 }
-                continue;
-            }
 
 
-            // manually versioned package
-            if (isset($data['version'])) {
-                $data['version_normalized'] = $this->versionParser->normalize($data['version']);
-            } else {
-                // auto-versionned package, read value from tag
-                $data['version'] = $tag;
-                $data['version_normalized'] = $parsedTag;
-            }
+                // make sure tag packages have no -dev flag
+                $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
+                $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
 
 
-            // make sure tag packages have no -dev flag
-            $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
-            $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
+                // broken package, version doesn't match tag
+                if ($data['version_normalized'] !== $parsedTag) {
+                    if ($verbose) {
+                        $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json');
+                    }
+                    continue;
+                }
 
 
-            // broken package, version doesn't match tag
-            if ($data['version_normalized'] !== $parsedTag) {
                 if ($verbose) {
                 if ($verbose) {
-                    $this->io->write('Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json');
+                    $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                 }
                 }
-                continue;
-            }
 
 
-            if ($verbose) {
-                $this->io->write('Importing tag '.$tag.' ('.$data['version_normalized'].')');
+                $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
+            } catch (\Exception $e) {
+                if ($verbose) {
+                    $this->io->write('Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()));
+                }
+                continue;
             }
             }
-
-            $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
         }
         }
 
 
         $this->io->overwrite('', false);
         $this->io->overwrite('', false);
 
 
         foreach ($driver->getBranches() as $branch => $identifier) {
         foreach ($driver->getBranches() as $branch => $identifier) {
-            $msg = 'Get composer info for <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
+            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
             if ($verbose) {
             if ($verbose) {
                 $this->io->write($msg);
                 $this->io->write($msg);
             } else {
             } else {
@@ -182,6 +184,23 @@ class VcsRepository extends ArrayRepository
                     }
                     }
                     continue;
                     continue;
                 }
                 }
+
+                // branches are always auto-versionned, read value from branch name
+                $data['version'] = $branch;
+                $data['version_normalized'] = $parsedBranch;
+
+                // make sure branch packages have a dev flag
+                if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
+                    $data['version'] = 'dev-' . $data['version'];
+                } else {
+                    $data['version'] = preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
+                }
+
+                if ($verbose) {
+                    $this->io->write('Importing branch '.$branch.' ('.$data['version'].')');
+                }
+
+                $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
             } catch (TransportException $e) {
             } catch (TransportException $e) {
                 if ($verbose) {
                 if ($verbose) {
                     $this->io->write('Skipped branch '.$branch.', no composer file was found');
                     $this->io->write('Skipped branch '.$branch.', no composer file was found');
@@ -191,23 +210,6 @@ class VcsRepository extends ArrayRepository
                 $this->io->write('Skipped branch '.$branch.', '.$e->getMessage());
                 $this->io->write('Skipped branch '.$branch.', '.$e->getMessage());
                 continue;
                 continue;
             }
             }
-
-            // branches are always auto-versionned, read value from branch name
-            $data['version'] = $branch;
-            $data['version_normalized'] = $parsedBranch;
-
-            // make sure branch packages have a dev flag
-            if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
-                $data['version'] = 'dev-' . $data['version'];
-            } else {
-                $data['version'] = preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
-            }
-
-            if ($verbose) {
-                $this->io->write('Importing branch '.$branch.' ('.$data['version'].')');
-            }
-
-            $this->addPackage($loader->load($this->preProcess($driver, $data, $identifier)));
         }
         }
 
 
         $this->io->overwrite('', false);
         $this->io->overwrite('', false);

+ 34 - 6
src/Composer/Util/StreamContextFactory.php

@@ -34,23 +34,51 @@ final class StreamContextFactory
         // Handle system proxy
         // Handle system proxy
         if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) {
         if (isset($_SERVER['HTTP_PROXY']) || isset($_SERVER['http_proxy'])) {
             // Some systems seem to rely on a lowercased version instead...
             // Some systems seem to rely on a lowercased version instead...
-            $proxy = isset($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY'];
+            $proxy = parse_url(isset($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
+        }
+
+        if (!empty($proxy)) {
+            $proxyURL = (isset($proxy['scheme']) ? $proxy['scheme'] : '') . '://';
+            $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';
+
+            if (isset($proxy['port'])) {
+                $proxyURL .= ":" . $proxy['port'];
+            } elseif ('http://' == substr($proxyURL, 0, 7)) {
+                $proxyURL .= ":80";
+            } elseif ('https://' == substr($proxyURL, 0, 8)) {
+                $proxyURL .= ":443";
+            }
 
 
             // http(s):// is not supported in proxy
             // http(s):// is not supported in proxy
-            $proxy = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxy);
+            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);
 
 
-            if (0 === strpos($proxy, 'ssl:') && !extension_loaded('openssl')) {
+            if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
                 throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
                 throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
             }
             }
 
 
             $options['http'] = array(
             $options['http'] = array(
-                'proxy'           => $proxy,
+                'proxy'           => $proxyURL,
                 'request_fulluri' => true,
                 'request_fulluri' => true,
             );
             );
+
+            if (isset($proxy['user'])) {
+                $auth = $proxy['user'];
+                if (isset($proxy['pass'])) {
+                    $auth .= ':' . $proxy['pass'];
+                }
+                $auth = base64_encode($auth);
+
+                // Preserve headers if already set in default options
+                if (isset($defaultOptions['http']['header'])) {
+                    $defaultOptions['http']['header'] .= "Proxy-Authorization: Basic {$auth}\r\n";
+                } else {
+                    $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
+                }
+            }
         }
         }
 
 
-        $options = array_merge_recursive($options, $defaultOptions);
-        
+        $options = array_replace_recursive($options, $defaultOptions);
+
         return stream_context_create($options, $defaultParams);
         return stream_context_create($options, $defaultParams);
     }
     }
 }
 }

+ 25 - 25
src/bootstrap.php

@@ -1,25 +1,25 @@
-<?php
-
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-function includeIfExists($file) {
-    if (file_exists($file)) {
-        return include $file;
-    }
-}
-
-if ((!$loader = includeIfExists(__DIR__.'/../vendor/.composer/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../.composer/autoload.php'))) {
-    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
-        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
-        'php composer.phar install'.PHP_EOL);
-}
-
-return $loader;
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+function includeIfExists($file) {
+    if (file_exists($file)) {
+        return include $file;
+    }
+}
+
+if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {
+    die('You must set up the project dependencies, run the following commands:'.PHP_EOL.
+        'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
+        'php composer.phar install'.PHP_EOL);
+}
+
+return $loader;

+ 75 - 53
tests/Composer/Test/Autoload/AutoloadGeneratorTest.php

@@ -60,14 +60,14 @@ class AutoloadGeneratorTest extends TestCase
     protected function tearDown()
     protected function tearDown()
     {
     {
         if ($this->vendorDir === $this->workingDir) {
         if ($this->vendorDir === $this->workingDir) {
-            if (is_dir($this->workingDir.'/.composer')) {
-                $this->fs->removeDirectory($this->workingDir.'/.composer');
+            if (is_dir($this->workingDir.'/composer')) {
+                $this->fs->removeDirectory($this->workingDir.'/composer');
             }
             }
         } elseif (is_dir($this->vendorDir)) {
         } elseif (is_dir($this->vendorDir)) {
             $this->fs->removeDirectory($this->vendorDir);
             $this->fs->removeDirectory($this->vendorDir);
         }
         }
-        if (is_dir($this->workingDir.'/.composersrc')) {
-            $this->fs->removeDirectory($this->workingDir.'/.composersrc');
+        if (is_dir($this->workingDir.'/composersrc')) {
+            $this->fs->removeDirectory($this->workingDir.'/composersrc');
         }
         }
 
 
         chdir($this->dir);
         chdir($this->dir);
@@ -78,22 +78,22 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')),
             'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
         ));
 
 
         $this->repository->expects($this->once())
         $this->repository->expects($this->once())
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue(array()));
             ->will($this->returnValue(array()));
 
 
-        if (!is_dir($this->vendorDir.'/.composer')) {
-            mkdir($this->vendorDir.'/.composer');
+        if (!is_dir($this->vendorDir.'/composer')) {
+            mkdir($this->vendorDir.'/composer');
         }
         }
 
 
         $this->createClassFile($this->workingDir);
         $this->createClassFile($this->workingDir);
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('main', $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('classmap', $this->vendorDir.'/.composer', 'classmap');
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('main', $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('classmap', $this->vendorDir.'/composer', 'classmap');
     }
     }
 
 
     public function testVendorDirSameAsWorkingDir()
     public function testVendorDirSameAsWorkingDir()
@@ -103,22 +103,22 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
         ));
 
 
         $this->repository->expects($this->once())
         $this->repository->expects($this->once())
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue(array()));
             ->will($this->returnValue(array()));
 
 
-        if (!is_dir($this->vendorDir.'/.composer')) {
-            mkdir($this->vendorDir.'/.composer', 0777, true);
+        if (!is_dir($this->vendorDir.'/composer')) {
+            mkdir($this->vendorDir.'/composer', 0777, true);
         }
         }
 
 
         $this->createClassFile($this->vendorDir);
         $this->createClassFile($this->vendorDir);
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('main3', $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/.composer', 'classmap');
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('main3', $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('classmap3', $this->vendorDir.'/composer', 'classmap');
     }
     }
 
 
     public function testMainPackageAutoloadingAlternativeVendorDir()
     public function testMainPackageAutoloadingAlternativeVendorDir()
@@ -126,7 +126,7 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
         ));
 
 
         $this->repository->expects($this->once())
         $this->repository->expects($this->once())
@@ -134,11 +134,27 @@ class AutoloadGeneratorTest extends TestCase
             ->will($this->returnValue(array()));
             ->will($this->returnValue(array()));
 
 
         $this->vendorDir .= '/subdir';
         $this->vendorDir .= '/subdir';
-        mkdir($this->vendorDir.'/.composer', 0777, true);
+        mkdir($this->vendorDir.'/composer', 0777, true);
         $this->createClassFile($this->workingDir);
         $this->createClassFile($this->workingDir);
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('main2', $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/.composer', 'classmap');
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('main2', $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('classmap2', $this->vendorDir.'/composer', 'classmap');
+    }
+
+    public function testMainPackageAutoloadingWithTargetDir()
+    {
+        $package = new MemoryPackage('a', '1.0', '1.0');
+        $package->setAutoload(array(
+            'psr-0' => array('Main\\Foo' => '', 'Main\\Bar' => ''),
+        ));
+        $package->setTargetDir('Main/Foo/');
+
+        $this->repository->expects($this->once())
+            ->method('getPackages')
+            ->will($this->returnValue(array()));
+
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertFileEquals(__DIR__.'/Fixtures/autoload_target_dir.php', $this->vendorDir.'/autoload.php');
     }
     }
 
 
     public function testVendorsAutoloading()
     public function testVendorsAutoloading()
@@ -155,10 +171,10 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        mkdir($this->vendorDir.'/.composer', 0777, true);
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('vendors', $this->vendorDir.'/.composer');
-        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
+        mkdir($this->vendorDir.'/composer', 0777, true);
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('vendors', $this->vendorDir.'/composer');
+        $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated, even if empty.");
     }
     }
 
 
     public function testVendorsClassMapAutoloading()
     public function testVendorsClassMapAutoloading()
@@ -175,7 +191,7 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        @mkdir($this->vendorDir.'/.composer', 0777, true);
+        @mkdir($this->vendorDir.'/composer', 0777, true);
         mkdir($this->vendorDir.'/a/a/src', 0777, true);
         mkdir($this->vendorDir.'/a/a/src', 0777, true);
         mkdir($this->vendorDir.'/b/b/src', 0777, true);
         mkdir($this->vendorDir.'/b/b/src', 0777, true);
         mkdir($this->vendorDir.'/b/b/lib', 0777, true);
         mkdir($this->vendorDir.'/b/b/lib', 0777, true);
@@ -183,17 +199,17 @@ class AutoloadGeneratorTest extends TestCase
         file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
         file_put_contents($this->vendorDir.'/b/b/src/b.php', '<?php class ClassMapBar {}');
         file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
         file_put_contents($this->vendorDir.'/b/b/lib/c.php', '<?php class ClassMapBaz {}');
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
         $this->assertEquals(
         $this->assertEquals(
             array(
             array(
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.php',
                 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/b/b/lib/c.php',
                 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/b/b/lib/c.php',
             ),
             ),
-            include ($this->vendorDir.'/.composer/autoload_classmap.php')
+            include ($this->vendorDir.'/composer/autoload_classmap.php')
         );
         );
-        $this->assertAutoloadFiles('classmap4', $this->vendorDir.'/.composer', 'classmap');
+        $this->assertAutoloadFiles('classmap4', $this->vendorDir.'/composer', 'classmap');
     }
     }
 
 
     public function testClassMapAutoloadingEmptyDirAndExactFile()
     public function testClassMapAutoloadingEmptyDirAndExactFile()
@@ -212,7 +228,7 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        @mkdir($this->vendorDir.'/.composer', 0777, true);
+        @mkdir($this->vendorDir.'/composer', 0777, true);
         mkdir($this->vendorDir.'/a/a/src', 0777, true);
         mkdir($this->vendorDir.'/a/a/src', 0777, true);
         mkdir($this->vendorDir.'/b/b', 0777, true);
         mkdir($this->vendorDir.'/b/b', 0777, true);
         mkdir($this->vendorDir.'/c/c/foo', 0777, true);
         mkdir($this->vendorDir.'/c/c/foo', 0777, true);
@@ -220,17 +236,17 @@ class AutoloadGeneratorTest extends TestCase
         file_put_contents($this->vendorDir.'/b/b/test.php', '<?php class ClassMapBar {}');
         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 {}');
         file_put_contents($this->vendorDir.'/c/c/foo/test.php', '<?php class ClassMapBaz {}');
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertTrue(file_exists($this->vendorDir.'/.composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertTrue(file_exists($this->vendorDir.'/composer/autoload_classmap.php'), "ClassMap file needs to be generated.");
         $this->assertEquals(
         $this->assertEquals(
             array(
             array(
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/test.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/test.php',
                 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/c/c/foo/test.php',
                 'ClassMapBaz' => $this->workingDir.'/composer-test-autoload/c/c/foo/test.php',
             ),
             ),
-            include ($this->vendorDir.'/.composer/autoload_classmap.php')
+            include ($this->vendorDir.'/composer/autoload_classmap.php')
         );
         );
-        $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/.composer', 'classmap');
+        $this->assertAutoloadFiles('classmap5', $this->vendorDir.'/composer', 'classmap');
     }
     }
 
 
     public function testOverrideVendorsAutoloading()
     public function testOverrideVendorsAutoloading()
@@ -248,9 +264,9 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->method('getPackages')
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        mkdir($this->vendorDir.'/.composer', 0777, true);
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/.composer');
-        $this->assertAutoloadFiles('override_vendors', $this->vendorDir.'/.composer');
+        mkdir($this->vendorDir.'/composer', 0777, true);
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir.'/composer');
+        $this->assertAutoloadFiles('override_vendors', $this->vendorDir.'/composer');
     }
     }
 
 
     public function testIncludePathFileGeneration()
     public function testIncludePathFileGeneration()
@@ -264,23 +280,29 @@ class AutoloadGeneratorTest extends TestCase
         $b = new MemoryPackage("b/b", "1.0", "1.0");
         $b = new MemoryPackage("b/b", "1.0", "1.0");
         $b->setIncludePaths(array("library"));
         $b->setIncludePaths(array("library"));
 
 
+        $c = new MemoryPackage("c", "1.0", "1.0");
+        $c->setIncludePaths(array("library"));
+
         $packages[] = $a;
         $packages[] = $a;
         $packages[] = $b;
         $packages[] = $b;
+        $packages[] = $c;
 
 
         $this->repository->expects($this->once())
         $this->repository->expects($this->once())
             ->method("getPackages")
             ->method("getPackages")
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        mkdir($this->vendorDir."/.composer", 0777, true);
+        mkdir($this->vendorDir."/composer", 0777, true);
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer");
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer");
 
 
+        $this->assertFileEquals(__DIR__.'/Fixtures/include_paths.php', $this->vendorDir.'/composer/include_paths.php');
         $this->assertEquals(
         $this->assertEquals(
             array(
             array(
-                $this->vendorDir."/a/a/lib/",
-                $this->vendorDir."/b/b/library"
+                $this->vendorDir."/a/a/lib",
+                $this->vendorDir."/b/b/library",
+                $this->vendorDir."/c/library",
             ),
             ),
-            require($this->vendorDir."/.composer/include_paths.php")
+            require($this->vendorDir."/composer/include_paths.php")
         );
         );
     }
     }
 
 
@@ -298,16 +320,16 @@ class AutoloadGeneratorTest extends TestCase
             ->method("getPackages")
             ->method("getPackages")
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        mkdir($this->vendorDir."/.composer", 0777, true);
+        mkdir($this->vendorDir."/composer", 0777, true);
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer");
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer");
 
 
         $oldIncludePath = get_include_path();
         $oldIncludePath = get_include_path();
 
 
-        require($this->vendorDir."/.composer/autoload.php");
+        require($this->vendorDir."/autoload.php");
 
 
         $this->assertEquals(
         $this->assertEquals(
-            $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib/",
+            $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib",
             get_include_path()
             get_include_path()
         );
         );
 
 
@@ -326,20 +348,20 @@ class AutoloadGeneratorTest extends TestCase
             ->method("getPackages")
             ->method("getPackages")
             ->will($this->returnValue($packages));
             ->will($this->returnValue($packages));
 
 
-        mkdir($this->vendorDir."/.composer", 0777, true);
+        mkdir($this->vendorDir."/composer", 0777, true);
 
 
-        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/.composer");
+        $this->generator->dump($this->repository, $package, $this->im, $this->vendorDir."/composer");
 
 
-        $this->assertFalse(file_exists($this->vendorDir."/.composer/include_paths.php"));
+        $this->assertFalse(file_exists($this->vendorDir."/composer/include_paths.php"));
     }
     }
 
 
     private function createClassFile($basedir)
     private function createClassFile($basedir)
     {
     {
-        if (!is_dir($basedir.'/.composersrc')) {
-            mkdir($basedir.'/.composersrc', 0777, true);
+        if (!is_dir($basedir.'/composersrc')) {
+            mkdir($basedir.'/composersrc', 0777, true);
         }
         }
 
 
-        file_put_contents($basedir.'/.composersrc/foo.php', '<?php class ClassMapFoo {}');
+        file_put_contents($basedir.'/composersrc/foo.php', '<?php class ClassMapFoo {}');
     }
     }
 
 
     private function assertAutoloadFiles($name, $dir, $type = 'namespaces')
     private function assertAutoloadFiles($name, $dir, $type = 'namespaces')

+ 1 - 1
tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php

@@ -6,5 +6,5 @@ $vendorDir = dirname(__DIR__);
 $baseDir = dirname($vendorDir);
 $baseDir = dirname($vendorDir);
 
 
 return array(
 return array(
-    'ClassMapFoo' => $baseDir . '/.composersrc/foo.php',
+    'ClassMapFoo' => $baseDir . '/composersrc/foo.php',
 );
 );

+ 1 - 1
tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php

@@ -6,5 +6,5 @@ $vendorDir = dirname(__DIR__);
 $baseDir = dirname(dirname($vendorDir));
 $baseDir = dirname(dirname($vendorDir));
 
 
 return array(
 return array(
-    'ClassMapFoo' => $baseDir . '/.composersrc/foo.php',
+    'ClassMapFoo' => $baseDir . '/composersrc/foo.php',
 );
 );

+ 1 - 1
tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php

@@ -6,5 +6,5 @@ $vendorDir = dirname(__DIR__);
 $baseDir = $vendorDir;
 $baseDir = $vendorDir;
 
 
 return array(
 return array(
-    'ClassMapFoo' => $baseDir . '/.composersrc/foo.php',
+    'ClassMapFoo' => $baseDir . '/composersrc/foo.php',
 );
 );

+ 41 - 0
tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php

@@ -0,0 +1,41 @@
+<?php
+
+// autoload.php generated by Composer
+if (!class_exists('Composer\\Autoload\\ClassLoader', false)) {
+    require __DIR__ . '/composer' . '/ClassLoader.php';
+}
+
+return call_user_func(function() {
+    $loader = new \Composer\Autoload\ClassLoader();
+    $composerDir = __DIR__ . '/composer';
+
+    $map = require $composerDir . '/autoload_namespaces.php';
+    foreach ($map as $namespace => $path) {
+        $loader->add($namespace, $path);
+    }
+
+    $classMap = require $composerDir . '/autoload_classmap.php';
+    if ($classMap) {
+        $loader->addClassMap($classMap);
+    }
+
+    spl_autoload_register(function($class) {
+        $dir = dirname(__DIR__) . '/';
+        $prefixes = array('Main\\Foo', 'Main\\Bar');
+        foreach ($prefixes as $prefix) {
+            if (0 !== strpos($class, $prefix)) {
+                continue;
+            }
+            $path = $dir . implode('/', array_slice(explode('\\', $class), 2)).'.php';
+            if (!stream_resolve_include_path($path)) {
+                return false;
+            }
+            require_once $path;
+            return true;
+        }
+    });
+
+    $loader->register();
+
+    return $loader;
+});

+ 12 - 0
tests/Composer/Test/Autoload/Fixtures/include_paths.php

@@ -0,0 +1,12 @@
+<?php
+
+// include_paths.php generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    $vendorDir . '/a/a/lib',
+    $vendorDir . '/b/b/library',
+    $vendorDir . '/c/library',
+);

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

@@ -18,6 +18,7 @@ use Composer\DependencyResolver\DefaultPolicy;
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\Literal;
 use Composer\DependencyResolver\Literal;
 use Composer\Package\Link;
 use Composer\Package\Link;
+use Composer\Package\AliasPackage;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Test\TestCase;
 use Composer\Test\TestCase;
 
 
@@ -99,6 +100,35 @@ class DefaultPolicyTest extends TestCase
         $this->assertEquals($expected, $selected);
         $this->assertEquals($expected, $selected);
     }
     }
 
 
+    public function testSelectLocalReposFirst()
+    {
+        $this->repoImportant = new ArrayRepository;
+
+        $this->repo->addPackage($packageA = $this->getPackage('A', 'dev-master'));
+        $this->repo->addPackage($packageAAlias = new AliasPackage($packageA, '2.1.9999999.9999999-dev', '2.1.x-dev'));
+        $this->repoImportant->addPackage($packageAImportant = $this->getPackage('A', 'dev-feature-a'));
+        $this->repoImportant->addPackage($packageAAliasImportant = new AliasPackage($packageAImportant, '2.1.9999999.9999999-dev', '2.1.x-dev'));
+        $this->repoImportant->addPackage($packageA2Important = $this->getPackage('A', 'dev-master'));
+        $this->repoImportant->addPackage($packageA2AliasImportant = new AliasPackage($packageA2Important, '2.1.9999999.9999999-dev', '2.1.x-dev'));
+        $packageAAliasImportant->setRootPackageAlias(true);
+
+        $this->pool->addRepository($this->repoInstalled);
+        $this->pool->addRepository($this->repoImportant);
+        $this->pool->addRepository($this->repo);
+
+        $packages = $this->pool->whatProvides('a', new VersionConstraint('=', '2.1.9999999.9999999-dev'));
+        $literals = array();
+        foreach ($packages as $package) {
+            $literals[] = new Literal($package, true);
+        }
+
+        $expected = array(new Literal($packageAAliasImportant, true));
+
+        $selected = $this->policy->selectPreferedPackages($this->pool, array(), $literals);
+
+        $this->assertEquals($expected, $selected);
+    }
+
     public function testSelectAllProviders()
     public function testSelectAllProviders()
     {
     {
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));

+ 26 - 4
tests/Composer/Test/Downloader/DownloadManagerTest.php

@@ -16,10 +16,17 @@ use Composer\Downloader\DownloadManager;
 
 
 class DownloadManagerTest extends \PHPUnit_Framework_TestCase
 class DownloadManagerTest extends \PHPUnit_Framework_TestCase
 {
 {
+    protected $filesystem;
+
+    public function setUp()
+    {
+        $this->filesystem = $this->getMock('Composer\Util\Filesystem');
+    }
+
     public function testSetGetDownloader()
     public function testSetGetDownloader()
     {
     {
         $downloader = $this->createDownloaderMock();
         $downloader = $this->createDownloaderMock();
-        $manager    = new DownloadManager();
+        $manager    = new DownloadManager(false, $this->filesystem);
 
 
         $manager->setDownloader('test', $downloader);
         $manager->setDownloader('test', $downloader);
         $this->assertSame($downloader, $manager->getDownloader('test'));
         $this->assertSame($downloader, $manager->getDownloader('test'));
@@ -36,7 +43,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->method('getInstallationSource')
             ->method('getInstallationSource')
             ->will($this->returnValue(null));
             ->will($this->returnValue(null));
 
 
-        $manager = new DownloadManager();
+        $manager = new DownloadManager(false, $this->filesystem);
 
 
         $this->setExpectedException('InvalidArgumentException');
         $this->setExpectedException('InvalidArgumentException');
 
 
@@ -62,6 +69,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('dist'));
             ->will($this->returnValue('dist'));
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloader'))
             ->setMethods(array('getDownloader'))
             ->getMock();
             ->getMock();
 
 
@@ -93,6 +101,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('source'));
             ->will($this->returnValue('source'));
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloader'))
             ->setMethods(array('getDownloader'))
             ->getMock();
             ->getMock();
 
 
@@ -126,6 +135,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('source'));
             ->will($this->returnValue('source'));
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloader'))
             ->setMethods(array('getDownloader'))
             ->getMock();
             ->getMock();
 
 
@@ -157,6 +167,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('dist'));
             ->will($this->returnValue('dist'));
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloader'))
             ->setMethods(array('getDownloader'))
             ->getMock();
             ->getMock();
 
 
@@ -195,6 +206,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -218,7 +230,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->method('getDistType')
             ->method('getDistType')
             ->will($this->returnValue(null));
             ->will($this->returnValue(null));
 
 
-        $manager = new DownloadManager();
+        $manager = new DownloadManager(false, $this->filesystem);
 
 
         $this->setExpectedException('InvalidArgumentException');
         $this->setExpectedException('InvalidArgumentException');
         $manager->download($package, 'target_dir');
         $manager->download($package, 'target_dir');
@@ -248,6 +260,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -283,6 +296,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -318,6 +332,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -354,6 +369,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -390,6 +406,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'target_dir');
             ->with($package, 'target_dir');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -414,7 +431,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->method('getDistType')
             ->method('getDistType')
             ->will($this->returnValue(null));
             ->will($this->returnValue(null));
 
 
-        $manager = new DownloadManager();
+        $manager = new DownloadManager(false, $this->filesystem);
         $manager->setPreferSource(true);
         $manager->setPreferSource(true);
 
 
         $this->setExpectedException('InvalidArgumentException');
         $this->setExpectedException('InvalidArgumentException');
@@ -450,6 +467,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($initial, $target, 'vendor/bundles/FOS/UserBundle');
             ->with($initial, $target, 'vendor/bundles/FOS/UserBundle');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -486,6 +504,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($initial, 'vendor/bundles/FOS/UserBundle');
             ->with($initial, 'vendor/bundles/FOS/UserBundle');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -526,6 +545,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($initial, $target, 'vendor/pkg');
             ->with($initial, $target, 'vendor/pkg');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -562,6 +582,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($initial, 'vendor/pkg');
             ->with($initial, 'vendor/pkg');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->setMethods(array('getDownloaderForInstalledPackage', 'download'))
             ->getMock();
             ->getMock();
         $manager
         $manager
@@ -588,6 +609,7 @@ class DownloadManagerTest extends \PHPUnit_Framework_TestCase
             ->with($package, 'vendor/bundles/FOS/UserBundle');
             ->with($package, 'vendor/bundles/FOS/UserBundle');
 
 
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
         $manager = $this->getMockBuilder('Composer\Downloader\DownloadManager')
+            ->setConstructorArgs(array(false, $this->filesystem))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->setMethods(array('getDownloaderForInstalledPackage'))
             ->getMock();
             ->getMock();
         $manager
         $manager

+ 3 - 3
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -140,7 +140,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
     public function testUpdate()
     public function testUpdate()
     {
     {
         $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'");
         $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'");
-        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no");
 
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
@@ -173,7 +173,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     {
     {
         $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'");
         $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url composer 'git://github.com/composer/composer' && git fetch composer && git fetch --tags composer && git checkout 'ref' && git reset --hard 'ref'");
-        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no");
 
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
         $packageMock->expects($this->any())
@@ -202,7 +202,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
 
     public function testRemove()
     public function testRemove()
     {
     {
-        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
+        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain --untracked-files=no");
 
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');

+ 115 - 0
tests/Composer/Test/InstallerTest.php

@@ -0,0 +1,115 @@
+<?php
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test;
+
+use Composer\Installer;
+use Composer\Repository\ArrayRepository;
+use Composer\Repository\RepositoryManager;
+use Composer\Repository\RepositoryInterface;
+use Composer\Package\PackageInterface;
+use Composer\Package\Link;
+use Composer\Test\Mock\WritableRepositoryMock;
+use Composer\Test\Mock\InstallationManagerMock;
+
+class InstallerTest extends TestCase
+{
+    /**
+     * @dataProvider provideInstaller
+     */
+    public function testInstaller(PackageInterface $rootPackage, $repositories, array $options)
+    {
+        $io = $this->getMock('Composer\IO\IOInterface');
+
+        $downloadManager = $this->getMock('Composer\Downloader\DownloadManager');
+        $config = $this->getMock('Composer\Config');
+
+        $repositoryManager = new RepositoryManager($io, $config);
+        $repositoryManager->setLocalRepository(new WritableRepositoryMock());
+        $repositoryManager->setLocalDevRepository(new WritableRepositoryMock());
+
+        if (!is_array($repositories)) {
+            $repositories = array($repositories);
+        }
+        foreach ($repositories as $repository) {
+            $repositoryManager->addRepository($repository);
+        }
+
+        $locker = $this->getMockBuilder('Composer\Package\Locker')->disableOriginalConstructor()->getMock();
+        $installationManager = new InstallationManagerMock();
+        $eventDispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')->disableOriginalConstructor()->getMock();
+        $autoloadGenerator = $this->getMock('Composer\Autoload\AutoloadGenerator');
+
+        $installer = new Installer($io, clone $rootPackage, $downloadManager, $repositoryManager, $locker, $installationManager, $eventDispatcher, $autoloadGenerator);
+        $result = $installer->run();
+        $this->assertTrue($result);
+
+        $expectedInstalled   = isset($options['install']) ? $options['install'] : array();
+        $expectedUpdated     = isset($options['update']) ? $options['update'] : array();
+        $expectedUninstalled = isset($options['uninstall']) ? $options['uninstall'] : array();
+
+        $installed = $installationManager->getInstalledPackages();
+        $this->assertSame($expectedInstalled, $installed);
+
+        $updated = $installationManager->getUpdatedPackages();
+        $this->assertSame($expectedUpdated, $updated);
+
+        $uninstalled = $installationManager->getUninstalledPackages();
+        $this->assertSame($expectedUninstalled, $uninstalled);
+    }
+
+    public function provideInstaller()
+    {
+        $cases = array();
+
+        // when A requires B and B requires A, and A is a non-published root package
+        // the install of B should succeed
+
+        $a = $this->getPackage('A', '1.0.0');
+        $a->setRequires(array(
+            new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
+        ));
+        $b = $this->getPackage('B', '1.0.0');
+        $b->setRequires(array(
+            new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
+        ));
+
+        $cases[] = array(
+            $a,
+            new ArrayRepository(array($b)),
+            array(
+                'install' => array($b)
+            ),
+        );
+
+        // #480: when A requires B and B requires A, and A is a published root package
+        // only B should be installed, as A is the root
+
+        $a = $this->getPackage('A', '1.0.0');
+        $a->setRequires(array(
+            new Link('A', 'B', $this->getVersionConstraint('=', '1.0.0')),
+        ));
+        $b = $this->getPackage('B', '1.0.0');
+        $b->setRequires(array(
+            new Link('B', 'A', $this->getVersionConstraint('=', '1.0.0')),
+        ));
+
+        $cases[] = array(
+            $a,
+            new ArrayRepository(array($a, $b)),
+            array(
+                'install' => array($b)
+            ),
+        );
+
+        return $cases;
+    }
+}

+ 14 - 0
tests/Composer/Test/Json/JsonFileTest.php

@@ -128,6 +128,20 @@ class JsonFileTest extends \PHPUnit_Framework_TestCase
         $this->assertJsonFormat($json, $data);
         $this->assertJsonFormat($json, $data);
     }
     }
 
 
+    public function testFormatEmptyArray()
+    {
+        $data = array('test' => array(), 'test2' => new \stdClass);
+        $json = '{
+    "test": [
+
+    ],
+    "test2": {
+
+    }
+}';
+        $this->assertJsonFormat($json, $data);
+    }
+
     public function testEscape()
     public function testEscape()
     {
     {
         $data = array("Metadata\\\"" => 'src/');
         $data = array("Metadata\\\"" => 'src/');

+ 56 - 0
tests/Composer/Test/Mock/InstallationManagerMock.php

@@ -0,0 +1,56 @@
+<?php
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Mock;
+
+use Composer\Installer\InstallationManager;
+use Composer\Repository\RepositoryInterface;
+use Composer\DependencyResolver\Operation\OperationInterface;
+use Composer\DependencyResolver\Operation\InstallOperation;
+use Composer\DependencyResolver\Operation\UpdateOperation;
+use Composer\DependencyResolver\Operation\UninstallOperation;
+
+class InstallationManagerMock extends InstallationManager
+{
+    private $installed = array();
+    private $updated = array();
+    private $uninstalled = array();
+
+    public function install(RepositoryInterface $repo, InstallOperation $operation)
+    {
+        $this->installed[] = $operation->getPackage();
+    }
+
+    public function update(RepositoryInterface $repo, UpdateOperation $operation)
+    {
+        $this->updated[] = array($operation->getInitialPackage(), $operation->getTargetPackage());
+    }
+
+    public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
+    {
+        $this->uninstalled[] = $operation->getPackage();
+    }
+
+    public function getInstalledPackages()
+    {
+        return $this->installed;
+    }
+
+    public function getUpdatedPackages()
+    {
+        return $this->updated;
+    }
+
+    public function getUninstalledPackages()
+    {
+        return $this->uninstalled;
+    }
+}

+ 26 - 0
tests/Composer/Test/Mock/WritableRepositoryMock.php

@@ -0,0 +1,26 @@
+<?php
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Test\Mock;
+
+use Composer\Repository\ArrayRepository;
+use Composer\Repository\WritableRepositoryInterface;
+
+class WritableRepositoryMock extends ArrayRepository implements WritableRepositoryInterface
+{
+    public function reload()
+    {
+    }
+
+    public function write()
+    {
+    }
+}

+ 1 - 1
tests/Composer/Test/Package/Loader/ArrayLoaderTest.php

@@ -89,7 +89,7 @@ class ArrayLoaderTest extends \PHPUnit_Framework_TestCase
             'homepage' => 'http://example.com',
             'homepage' => 'http://example.com',
             'license' => array('MIT', 'GPLv3'),
             'license' => array('MIT', 'GPLv3'),
             'authors' => array(
             'authors' => array(
-                array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org'),
+                array('name' => 'Bob', 'email' => 'bob@example.org', 'homepage' => 'example.org', 'role' => 'Developer'),
             ),
             ),
             'require' => array(
             'require' => array(
                 'foo/bar' => '1.0',
                 'foo/bar' => '1.0',

+ 12 - 4
tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php

@@ -15,6 +15,7 @@ namespace Composer\Test\Repository\Vcs;
 use Composer\Downloader\TransportException;
 use Composer\Downloader\TransportException;
 use Composer\Repository\Vcs\GitHubDriver;
 use Composer\Repository\Vcs\GitHubDriver;
 use Composer\Util\Filesystem;
 use Composer\Util\Filesystem;
+use Composer\Config;
 
 
 /**
 /**
  * @author Beau Simensen <beau@dflydev.com>
  * @author Beau Simensen <beau@dflydev.com>
@@ -64,7 +65,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
             ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false))
             ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false))
             ->will($this->returnValue('{"master_branch": "test_master"}'));
             ->will($this->returnValue('{"master_branch": "test_master"}'));
 
 
-        $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem);
+        $gitHubDriver = new GitHubDriver($repoUrl, $io, new Config(), null, $remoteFilesystem);
         $gitHubDriver->initialize();
         $gitHubDriver->initialize();
         $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha));
         $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha));
 
 
@@ -114,7 +115,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
             ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false))
             ->with($this->equalTo($repoUrl), $this->equalTo($repoApiUrl), $this->equalTo(false))
             ->will($this->returnValue('{"master_branch": "test_master"}'));
             ->will($this->returnValue('{"master_branch": "test_master"}'));
 
 
-        $gitHubDriver = new GitHubDriver($repoUrl, $io, null, $remoteFilesystem);
+        $gitHubDriver = new GitHubDriver($repoUrl, $io, new Config(), null, $remoteFilesystem);
         $gitHubDriver->initialize();
         $gitHubDriver->initialize();
         $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha));
         $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha));
 
 
@@ -171,7 +172,14 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
 
 
         // clean local clone if present
         // clean local clone if present
         $fs = new Filesystem();
         $fs = new Filesystem();
-        $fs->removeDirectory(sys_get_temp_dir() . '/composer-' . preg_replace('{[^a-z0-9.]}i', '-', $repoSshUrl) . '/');
+        $fs->removeDirectory(sys_get_temp_dir() . '/composer-test');
+
+        $config = new Config();
+        $config->merge(array(
+            'config' => array(
+                'home' => sys_get_temp_dir() . '/composer-test',
+            ),
+        ));
 
 
         $process->expects($this->at(0))
         $process->expects($this->at(0))
             ->method('execute')
             ->method('execute')
@@ -202,7 +210,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
             ->method('splitLines')
             ->method('splitLines')
             ->will($this->returnValue(array('* test_master')));
             ->will($this->returnValue(array('* test_master')));
 
 
-        $gitHubDriver = new GitHubDriver($repoUrl, $io, $process, $remoteFilesystem);
+        $gitHubDriver = new GitHubDriver($repoUrl, $io, $config, $process, $remoteFilesystem);
         $gitHubDriver->initialize();
         $gitHubDriver->initialize();
 
 
         $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier());
         $this->assertEquals('test_master', $gitHubDriver->getRootIdentifier());

+ 3 - 2
tests/Composer/Test/Repository/Vcs/SvnDriverTest.php

@@ -14,6 +14,7 @@ namespace Composer\Test\Repository\Vcs;
 
 
 use Composer\Repository\Vcs\SvnDriver;
 use Composer\Repository\Vcs\SvnDriver;
 use Composer\IO\NullIO;
 use Composer\IO\NullIO;
+use Composer\Config;
 
 
 class SvnDriverTest extends \PHPUnit_Framework_TestCase
 class SvnDriverTest extends \PHPUnit_Framework_TestCase
 {
 {
@@ -39,8 +40,8 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
             ->method('getErrorOutput')
             ->method('getErrorOutput')
             ->will($this->returnValue($output));
             ->will($this->returnValue($output));
 
 
-        $svn = new SvnDriver('http://till:secret@corp.svn.local/repo', $console, $process);
-        $svn->getTags();
+        $svn = new SvnDriver('http://till:secret@corp.svn.local/repo', $console, new Config(), $process);
+        $svn->initialize();
     }
     }
 
 
     private function getCmd($cmd)
     private function getCmd($cmd)

+ 2 - 1
tests/Composer/Test/Repository/VcsRepositoryTest.php

@@ -19,6 +19,7 @@ use Composer\Repository\Vcs\GitDriver;
 use Composer\Util\Filesystem;
 use Composer\Util\Filesystem;
 use Composer\Util\ProcessExecutor;
 use Composer\Util\ProcessExecutor;
 use Composer\IO\NullIO;
 use Composer\IO\NullIO;
+use Composer\Config;
 
 
 class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
 class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
 {
 {
@@ -123,7 +124,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
             'dev-master' => true,
             'dev-master' => true,
         );
         );
 
 
-        $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO);
+        $repo = new VcsRepository(array('url' => self::$gitRepo, 'type' => 'vcs'), new NullIO, new Config());
         $packages = $repo->getPackages();
         $packages = $repo->getPackages();
         $dumper = new ArrayDumper();
         $dumper = new ArrayDumper();
 
 

+ 9 - 6
tests/Composer/Test/TestCase.php

@@ -19,26 +19,29 @@ use Composer\Util\Filesystem;
 
 
 abstract class TestCase extends \PHPUnit_Framework_TestCase
 abstract class TestCase extends \PHPUnit_Framework_TestCase
 {
 {
-    private static $versionParser;
+    private static $parser;
 
 
-    public static function setUpBeforeClass()
+    protected static function getVersionParser()
     {
     {
-        if (!self::$versionParser) {
-            self::$versionParser = new VersionParser();
+        if (!self::$parser) {
+            self::$parser = new VersionParser();
         }
         }
+
+        return self::$parser;
     }
     }
 
 
     protected function getVersionConstraint($operator, $version)
     protected function getVersionConstraint($operator, $version)
     {
     {
         return new VersionConstraint(
         return new VersionConstraint(
             $operator,
             $operator,
-            self::$versionParser->normalize($version)
+            self::getVersionParser()->normalize($version)
         );
         );
     }
     }
 
 
     protected function getPackage($name, $version)
     protected function getPackage($name, $version)
     {
     {
-        $normVersion = self::$versionParser->normalize($version);
+        $normVersion = self::getVersionParser()->normalize($version);
+
         return new MemoryPackage($name, $normVersion, $version);
         return new MemoryPackage($name, $normVersion, $version);
     }
     }
 
 

+ 47 - 7
tests/Composer/Test/Util/StreamContextFactoryTest.php

@@ -57,31 +57,63 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
 
 
     public function testHttpProxy()
     public function testHttpProxy()
     {
     {
-        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:port/';
+        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:3128/';
         $_SERVER['HTTP_PROXY'] = 'http://proxyserver/';
         $_SERVER['HTTP_PROXY'] = 'http://proxyserver/';
 
 
         $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET')));
         $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET')));
         $options = stream_context_get_options($context);
         $options = stream_context_get_options($context);
 
 
-        $this->assertSame('http://proxyserver/', $_SERVER['HTTP_PROXY']);
+        $this->assertEquals(array('http' => array(
+            'proxy' => 'tcp://proxyserver.net:3128',
+            'request_fulluri' => true,
+            'method' => 'GET',
+            'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n"
+        )), $options);
+    }
+
+    public function testOptionsArePreserved()
+    {
+        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:3128/';
+
+        $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET', 'header' => "X-Foo: bar\r\n", 'request_fulluri' => false)));
+        $options = stream_context_get_options($context);
+
+        $this->assertEquals(array('http' => array(
+            'proxy' => 'tcp://proxyserver.net:3128',
+            'request_fulluri' => false,
+            'method' => 'GET',
+            'header' => "X-Foo: bar\r\nProxy-Authorization: Basic " . base64_encode('username:password') . "\r\n"
+        )), $options);
+    }
+
+    public function testHttpProxyWithoutPort()
+    {
+        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net';
+
+        $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET')));
+        $options = stream_context_get_options($context);
 
 
         $this->assertEquals(array('http' => array(
         $this->assertEquals(array('http' => array(
-            'proxy' => 'tcp://username:password@proxyserver.net:port/',
+            'proxy' => 'tcp://proxyserver.net:80',
             'request_fulluri' => true,
             'request_fulluri' => true,
             'method' => 'GET',
             'method' => 'GET',
+            'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n"
         )), $options);
         )), $options);
     }
     }
 
 
-    public function testSSLProxy()
+    /**
+     * @dataProvider dataSSLProxy
+     */
+    public function testSSLProxy($expected, $proxy)
     {
     {
-        $_SERVER['http_proxy'] = 'https://proxyserver/';
+        $_SERVER['http_proxy'] = $proxy;
 
 
         if (extension_loaded('openssl')) {
         if (extension_loaded('openssl')) {
             $context = StreamContextFactory::getContext();
             $context = StreamContextFactory::getContext();
             $options = stream_context_get_options($context);
             $options = stream_context_get_options($context);
 
 
-            $this->assertSame(array('http' => array(
-                'proxy' => 'ssl://proxyserver/',
+            $this->assertEquals(array('http' => array(
+                'proxy' => $expected,
                 'request_fulluri' => true,
                 'request_fulluri' => true,
             )), $options);
             )), $options);
         } else {
         } else {
@@ -93,4 +125,12 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
             }
             }
         }
         }
     }
     }
+
+    public function dataSSLProxy()
+    {
+        return array(
+            array('ssl://proxyserver:443', 'https://proxyserver/'),
+            array('ssl://proxyserver:8443', 'https://proxyserver:8443'),
+        );
+    }
 }
 }