Bladeren bron

Merged in upstream

Maxim Chernyshev 13 jaren geleden
bovenliggende
commit
2ca50a1ef3
80 gewijzigde bestanden met toevoegingen van 1304 en 452 verwijderingen
  1. 1 1
      .travis.yml
  2. 35 28
      CHANGELOG.md
  3. 4 4
      composer.lock
  4. 1 1
      doc/00-intro.md
  5. 6 6
      doc/01-basic-usage.md
  6. 8 0
      doc/02-libraries.md
  7. 15 5
      doc/03-cli.md
  8. 29 9
      doc/04-schema.md
  9. 63 12
      doc/05-repositories.md
  10. 90 0
      doc/articles/aliases.md
  11. 24 6
      doc/articles/handling-private-packages-with-satis.md
  12. 4 0
      res/composer-schema.json
  13. 109 38
      src/Composer/Autoload/AutoloadGenerator.php
  14. 5 3
      src/Composer/Command/CreateProjectCommand.php
  15. 3 2
      src/Composer/Command/InitCommand.php
  16. 2 1
      src/Composer/Command/SearchCommand.php
  17. 1 1
      src/Composer/Command/SelfUpdateCommand.php
  18. 14 4
      src/Composer/Command/ShowCommand.php
  19. 4 4
      src/Composer/Compiler.php
  20. 4 0
      src/Composer/Console/Application.php
  21. 38 2
      src/Composer/DependencyResolver/DefaultPolicy.php
  22. 1 1
      src/Composer/DependencyResolver/Solver.php
  23. 4 3
      src/Composer/Downloader/DownloadManager.php
  24. 6 4
      src/Composer/Downloader/GitDownloader.php
  25. 28 3
      src/Composer/Factory.php
  26. 8 0
      src/Composer/IO/ConsoleIO.php
  27. 7 0
      src/Composer/IO/IOInterface.php
  28. 8 0
      src/Composer/IO/NullIO.php
  29. 40 11
      src/Composer/Installer.php
  30. 21 21
      src/Composer/Installer/InstallationManager.php
  31. 4 4
      src/Composer/Installer/InstallerInstaller.php
  32. 9 9
      src/Composer/Installer/InstallerInterface.php
  33. 14 11
      src/Composer/Installer/LibraryInstaller.php
  34. 5 5
      src/Composer/Installer/MetapackageInstaller.php
  35. 9 21
      src/Composer/Installer/ProjectInstaller.php
  36. 1 1
      src/Composer/Json/JsonFile.php
  37. 30 0
      src/Composer/Package/AliasPackage.php
  38. 9 1
      src/Composer/Package/Loader/ArrayLoader.php
  39. 29 2
      src/Composer/Package/Loader/RootPackageLoader.php
  40. 12 2
      src/Composer/Package/Locker.php
  41. 7 0
      src/Composer/Repository/ArrayRepository.php
  42. 2 2
      src/Composer/Repository/ComposerRepository.php
  43. 1 1
      src/Composer/Repository/InstalledRepositoryInterface.php
  44. 21 11
      src/Composer/Repository/PearRepository.php
  45. 3 9
      src/Composer/Repository/Vcs/GitBitbucketDriver.php
  46. 4 7
      src/Composer/Repository/Vcs/GitDriver.php
  47. 4 16
      src/Composer/Repository/Vcs/GitHubDriver.php
  48. 3 9
      src/Composer/Repository/Vcs/HgBitbucketDriver.php
  49. 8 11
      src/Composer/Repository/Vcs/HgDriver.php
  50. 11 19
      src/Composer/Repository/Vcs/SvnDriver.php
  51. 5 2
      src/Composer/Repository/Vcs/VcsDriver.php
  52. 5 3
      src/Composer/Repository/VcsRepository.php
  53. 5 1
      src/Composer/Util/StreamContextFactory.php
  54. 25 25
      src/bootstrap.php
  55. 75 53
      tests/Composer/Test/Autoload/AutoloadGeneratorTest.php
  56. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap.php
  57. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap2.php
  58. 1 1
      tests/Composer/Test/Autoload/Fixtures/autoload_classmap3.php
  59. 41 0
      tests/Composer/Test/Autoload/Fixtures/autoload_target_dir.php
  60. 12 0
      tests/Composer/Test/Autoload/Fixtures/include_paths.php
  61. 30 0
      tests/Composer/Test/DependencyResolver/DefaultPolicyTest.php
  62. 26 4
      tests/Composer/Test/Downloader/DownloadManagerTest.php
  63. 18 10
      tests/Composer/Test/Downloader/GitDownloaderTest.php
  64. 5 5
      tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php
  65. 5 5
      tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php
  66. 5 5
      tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php
  67. 1 1
      tests/Composer/Test/Installer/InstallationManagerTest.php
  68. 1 1
      tests/Composer/Test/Installer/InstallerInstallerTest.php
  69. 1 1
      tests/Composer/Test/Installer/LibraryInstallerTest.php
  70. 1 1
      tests/Composer/Test/Installer/MetapackageInstallerTest.php
  71. 115 0
      tests/Composer/Test/InstallerTest.php
  72. 14 0
      tests/Composer/Test/Json/JsonFileTest.php
  73. 56 0
      tests/Composer/Test/Mock/InstallationManagerMock.php
  74. 26 0
      tests/Composer/Test/Mock/WritableRepositoryMock.php
  75. 1 1
      tests/Composer/Test/Package/Loader/ArrayLoaderTest.php
  76. 12 4
      tests/Composer/Test/Repository/Vcs/GitHubDriverTest.php
  77. 3 2
      tests/Composer/Test/Repository/Vcs/SvnDriverTest.php
  78. 2 1
      tests/Composer/Test/Repository/VcsRepositoryTest.php
  79. 9 6
      tests/Composer/Test/TestCase.php
  80. 33 7
      tests/Composer/Test/Util/StreamContextFactoryTest.php

+ 1 - 1
.travis.yml

@@ -7,6 +7,6 @@ php:
 
 before_script:
   - wget -nc http://getcomposer.org/composer.phar
-  - php composer.phar update
+  - php composer.phar install
 
 script: phpunit

+ 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

+ 4 - 4
composer.lock

@@ -1,5 +1,5 @@
 {
-    "hash": "c4a3809551d45254dd0b45eb282d6285",
+    "hash": "1350b672ef2fc5723334092cb18c3bca",
     "packages": [
         {
             "package": "justinrainbow/json-schema",
@@ -12,19 +12,19 @@
         {
             "package": "symfony/console",
             "version": "dev-master",
-            "source-reference": "8e3c42aa976f18a9bfcb0694553e5f99def3309c",
+            "source-reference": "eaad4427b10ff39402bce0ae4f8cd1faf2b6532a",
             "alias": "2.1.9999999.9999999-dev"
         },
         {
             "package": "symfony/finder",
             "version": "dev-master",
-            "source-reference": "57ec7198a70e6c40e450ba66cc2f8ecab98746c8",
+            "source-reference": "78b2e33951821b6d423718f57788f1894dcb935a",
             "alias": "2.1.9999999.9999999-dev"
         },
         {
             "package": "symfony/process",
             "version": "dev-master",
-            "source-reference": "2e4da8c8076744bafed97451bb1574c96cda0e68",
+            "source-reference": "718655f4bc664d693b33f3e6e8a895e454208021",
             "alias": "2.1.9999999.9999999-dev"
         }
     ],

+ 1 - 1
doc/00-intro.md

@@ -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
 process:
 
-    require 'vendor/.composer/autoload.php';
+    require 'vendor/autoload.php';
 
 Woh! Now start using monolog! To keep learning more about Composer, keep
 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
 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
 the latest matching versions (according to your `composer.json` file) and also update
 the lock file with the new version.
@@ -136,10 +136,10 @@ but it makes life quite a bit simpler.
 ## Autoloading
 
 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.
 
-    require 'vendor/.composer/autoload.php';
+    require 'vendor/autoload.php';
 
 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
@@ -168,13 +168,13 @@ be in your project root. An example filename would be `src/Acme/Foo.php`
 containing an `Acme\Foo` class.
 
 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
 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.
 
-    $loader = require 'vendor/.composer/autoload.php';
+    $loader = require 'vendor/autoload.php';
     $loader->add('Acme\Test', __DIR__);
 
 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.
 
 > **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.
 
 ← [Intro](00-intro.md)  |  [Libraries](02-libraries.md) →

+ 8 - 0
doc/02-libraries.md

@@ -76,6 +76,14 @@ Here are some examples of version branch names:
 > **Note:** When you install a dev version, it will install it from source.
 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-dev`, which would allow you to require `1.0-dev` in all
+the packages.
+
+See [Aliases](articles/aliases.md) for more information.
+
 ## Lock file
 
 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))
 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
 
 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
 `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;

+ 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.
 * **email:** The author's email address.
 * **homepage:** An URL to the author's website.
+* **role:** The authors' role in the project (e.g. developer or translator)
 
 An example:
 
@@ -153,12 +154,14 @@ An example:
             {
                 "name": "Nils Adermann",
                 "email": "naderman@naderman.de",
-                "homepage": "http://www.naderman.de"
+                "homepage": "http://www.naderman.de",
+                "role": "Developer"
             },
             {
                 "name": "Jordi Boggiano",
                 "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.
 
 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
-package root.
+package root. Note that this also supports the PEAR-style convention.
 
 Example:
 
     {
         "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:
 
     {
@@ -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
-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.
 
 Example:
 
     {
         "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
   can run before Composer assumes they died out. You may need to make this
   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:
 

+ 63 - 12
doc/05-repositories.md

@@ -54,15 +54,24 @@ want to learn why.
 ### Composer
 
 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 },
+                "1.0.x-dev": { @composer.json },
+                "0.0.1": { @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).
 
-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
 

+ 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
 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
 
@@ -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
 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
-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,
-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": [
@@ -29,7 +47,7 @@ make sure all versions are selected.
         "require": {
             "company/package": "*",
             "company/package2": "*",
-            "company/package3": "*"
+            "company/package3": "2.0.0"
         }
     }
 

+ 4 - 0
res/composer-schema.json

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

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

@@ -25,25 +25,26 @@ use Composer\Util\Filesystem;
  */
 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->ensureDirectoryExists($installationManager->getVendorPath());
         $filesystem->ensureDirectoryExists($targetDir);
         $vendorPath = strtr(realpath($installationManager->getVendorPath()), '\\', '/');
         $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
 <?php
 
 // autoload_namespace.php generated by Composer
 
-\$vendorDir = $vendorDirCode;
-\$baseDir = $appBaseDir;
+\$vendorDir = $vendorPathCode;
+\$baseDir = $appBaseDirCode;
 
 return array(
 
@@ -55,22 +56,7 @@ EOF;
         foreach ($autoloads['psr-0'] as $namespace => $paths) {
             $exportedPaths = array();
             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);
             $namespacesFile .= "    $exportedPrefix => ";
@@ -87,13 +73,44 @@ EOF;
 
 // autoload_classmap.php generated by Composer
 
-\$vendorDir = $vendorDirCode;
-\$baseDir = $appBaseDir;
+\$vendorDir = $vendorPathCode;
+\$baseDir = $appBaseDirCode;
 
 return array(
 
 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
         $autoloads['classmap'] = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($autoloads['classmap']));
         foreach ($autoloads['classmap'] as $dir) {
@@ -106,11 +123,23 @@ EOF;
 
         file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
         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.'/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');
+
+        // 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)
@@ -148,6 +177,10 @@ EOF;
             }
 
             foreach ($package->getAutoload() as $type => $mapping) {
+                // skip misconfigured packages
+                if (!is_array($mapping)) {
+                    continue;
+                }
                 foreach ($mapping as $namespace => $paths) {
                     foreach ((array) $paths as $path) {
                         $autoloads[$type][$namespace][] = empty($installPath) ? $path : $installPath.'/'.$path;
@@ -182,7 +215,7 @@ EOF;
         return $loader;
     }
 
-    protected function getIncludePathsFile(array $packageMap)
+    protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, $relVendorPath, $vendorPath, $vendorPathCode, $appBaseDirCode)
     {
         $includePaths = array();
 
@@ -194,6 +227,7 @@ EOF;
             }
 
             foreach ($package->getIncludePaths() as $includePath) {
+                $includePath = trim($includePath, '/');
                 $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath;
             }
         }
@@ -202,30 +236,65 @@ EOF;
             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
 
 // 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() {
-    $loader = new \Composer\Autoload\ClassLoader();
+    \$loader = new \\Composer\\Autoload\\ClassLoader();
+    \$composerDir = $vendorPathToTargetDirCode;
 
 
 HEADER;
 
         if ($useIncludePath) {
             $file .= <<<'INCLUDE_PATH'
-    $includePaths = require __DIR__.'/include_paths.php';
+    $includePaths = require $composerDir . '/include_paths.php';
     array_unshift($includePaths, get_include_path());
     set_include_path(join(PATH_SEPARATOR, $includePaths));
 
@@ -235,7 +304,7 @@ INCLUDE_PATH;
 
         if ($usePSR0) {
             $file .= <<<'PSR0'
-    $map = require __DIR__.'/autoload_namespaces.php';
+    $map = require $composerDir . '/autoload_namespaces.php';
     foreach ($map as $namespace => $path) {
         $loader->add($namespace, $path);
     }
@@ -246,7 +315,7 @@ PSR0;
 
         if ($useClassMap) {
             $file .= <<<'CLASSMAP'
-    $classMap = require __DIR__.'/autoload_classmap.php';
+    $classMap = require $composerDir . '/autoload_classmap.php';
     if ($classMap) {
         $loader->addClassMap($classMap);
     }
@@ -255,6 +324,8 @@ PSR0;
 CLASSMAP;
         }
 
+        $file .= $targetDirLoader;
+
         return $file . <<<'FOOTER'
     $loader->register();
 

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

@@ -18,6 +18,7 @@ use Composer\Installer\ProjectInstaller;
 use Composer\IO\IOInterface;
 use Composer\Repository\ComposerRepository;
 use Composer\Repository\FilesystemRepository;
+use Composer\Repository\InstalledFilesystemRepository;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
@@ -82,12 +83,13 @@ EOT
             $dm->setPreferSource(true);
         }
 
+        $config = Factory::createConfig();
         if (null === $repositoryUrl) {
-            $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $io);
+            $sourceRepo = new ComposerRepository(array('url' => 'http://packagist.org'), $io, $config);
         } elseif ("json" === pathinfo($repositoryUrl, PATHINFO_EXTENSION)) {
             $sourceRepo = new FilesystemRepository(new JsonFile($repositoryUrl, new RemoteFilesystem($io)));
         } elseif (0 === strpos($repositoryUrl, 'http')) {
-            $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io);
+            $sourceRepo = new ComposerRepository(array('url' => $repositoryUrl), $io, $config);
         } else {
             throw new \InvalidArgumentException("Invalid repository url given. Has to be a .json file or an http url.");
         }
@@ -112,7 +114,7 @@ EOT
 
         $io->write('<info>Installing ' . $package->getName() . ' as new project.</info>', true);
         $projectInstaller = new ProjectInstaller($directory, $dm);
-        $projectInstaller->install($package);
+        $projectInstaller->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), $package);
 
         $io->write('<info>Created project into directory ' . $directory . '</info>', true);
         chdir($directory);

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

@@ -13,6 +13,7 @@
 namespace Composer\Command;
 
 use Composer\Json\JsonFile;
+use Composer\Factory;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\ComposerRepository;
@@ -34,7 +35,7 @@ class InitCommand extends Command
     public function parseAuthorString($author)
     {
         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(
                     'name'  => trim($match['name']),
                     'email' => $match['email']
@@ -229,7 +230,7 @@ EOT
         if (!$this->repos) {
             $this->repos = new CompositeRepository(array(
                 new PlatformRepository,
-                new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO())
+                new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig())
             ));
         }
 

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

@@ -20,6 +20,7 @@ use Composer\Repository\PlatformRepository;
 use Composer\Repository\ComposerRepository;
 use Composer\Package\PackageInterface;
 use Composer\Package\AliasPackage;
+use Composer\Factory;
 
 /**
  * @author Robert Schönthal <seroscho@googlemail.com>
@@ -54,7 +55,7 @@ EOT
         } else {
             $output->writeln('No composer.json found in the current directory, showing packages from packagist.org');
             $installedRepo = $platformRepo;
-            $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO());
+            $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig());
             $repos = new CompositeRepository(array($installedRepo, $packagist));
         }
 

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

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

+ 14 - 4
src/Composer/Command/ShowCommand.php

@@ -13,6 +13,7 @@
 namespace Composer\Command;
 
 use Composer\Composer;
+use Composer\Factory;
 use Composer\Package\PackageInterface;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputArgument;
@@ -65,7 +66,7 @@ EOT
         } else {
             $output->writeln('No composer.json found in the current directory, showing packages from packagist.org');
             $installedRepo = $platformRepo;
-            $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO());
+            $packagist = new ComposerRepository(array('url' => 'http://packagist.org'), $this->getIO(), Factory::createConfig());
             $repos = new CompositeRepository(array($installedRepo, $packagist));
         }
 
@@ -78,7 +79,15 @@ EOT
 
             $this->printMeta($input, $output, $package, $installedRepo, $repos);
             $this->printLinks($input, $output, $package, 'requires');
-            $this->printLinks($input, $output, $package, 'recommends');
+            $this->printLinks($input, $output, $package, 'devRequires', 'requires (dev)');
+            if ($package->getSuggests()) {
+                $output->writeln("\n<info>suggests</info>");
+                foreach ($package->getSuggests() as $suggested => $reason) {
+                    $output->writeln($suggested . ' <comment>' . $reason . '</comment>');
+                }
+            }
+            $this->printLinks($input, $output, $package, 'provides');
+            $this->printLinks($input, $output, $package, 'conflicts');
             $this->printLinks($input, $output, $package, 'replaces');
             return;
         }
@@ -209,10 +218,11 @@ EOT
      *
      * @param string $linkType
      */
-    protected function printLinks(InputInterface $input, OutputInterface $output, PackageInterface $package, $linkType)
+    protected function printLinks(InputInterface $input, OutputInterface $output, PackageInterface $package, $linkType, $title = null)
     {
+        $title = $title ?: $linkType;
         if ($links = $package->{'get'.ucfirst($linkType)}()) {
-            $output->writeln("\n<info>" . $linkType . "</info>");
+            $output->writeln("\n<info>" . $title . "</info>");
 
             foreach ($links as $link) {
                 $output->writeln($link->getTarget() . ' <comment>' . $link->getPrettyConstraint() . '</comment>');

+ 4 - 4
src/Composer/Compiler.php

@@ -81,10 +81,10 @@ class Compiler
             $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);
 
         // Stubs

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

@@ -67,6 +67,10 @@ class Application extends BaseApplication
         $this->registerCommands();
         $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);
     }
 

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

@@ -69,6 +69,8 @@ class DefaultPolicy implements PolicyInterface
             $literals = $this->pruneToBestVersion($literals);
 
             $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);
+
+            $literals = $this->pruneRemoteAliases($literals);
         }
 
         $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)
     {
         $selected = array();
@@ -239,4 +241,38 @@ class DefaultPolicy implements PolicyInterface
 
         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;
+    }
 }

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

@@ -1615,7 +1615,7 @@ class Solver
 
             $foundDisabled = false;
             foreach ($problemRules as $problemRule) {
-                if ($problemRule->disabled()) {
+                if ($problemRule->isDisabled()) {
                     $foundDisabled = true;
                     break;
                 }

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

@@ -24,6 +24,7 @@ use Composer\Util\Filesystem;
 class DownloadManager
 {
     private $preferSource = false;
+    private $filesystem;
     private $downloaders  = array();
 
     /**
@@ -31,9 +32,10 @@ class DownloadManager
      *
      * @param   Boolean $preferSource   prefer downloading from source
      */
-    public function __construct($preferSource = false)
+    public function __construct($preferSource = false, Filesystem $filesystem = null)
     {
         $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');
         }
 
-        $fs = new Filesystem();
-        $fs->ensureDirectoryExists($targetDir);
+        $this->filesystem->ensureDirectoryExists($targetDir);
 
         $downloader = $this->getDownloaderForInstalledPackage($package);
         $downloader->download($package, $targetDir);

+ 6 - 4
src/Composer/Downloader/GitDownloader.php

@@ -26,7 +26,7 @@ class GitDownloader extends VcsDownloader
     public function doDownload(PackageInterface $package, $path)
     {
         $ref = $package->getSourceReference();
-        $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s';
+        $command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s && git remote add composer %1$s';
         $this->io->write("    Cloning ".$package->getSourceReference());
 
         $commandCallable = function($url) use ($ref, $path, $command) {
@@ -44,14 +44,16 @@ class GitDownloader extends VcsDownloader
     {
         $ref = $target->getSourceReference();
         $this->io->write("    Checking out ".$target->getSourceReference());
-        $command = 'cd %s && git remote set-url origin %s && git fetch origin && git fetch --tags origin && git checkout %3$s && git reset --hard %3$s';
+        $command = 'cd %s && git remote set-url composer %s && git fetch composer && git fetch --tags composer && git checkout %3$s && git reset --hard %3$s';
+
+        // TODO: BC for the composer remote that didn't exist, to be remove after May 18th.
+        $this->process->execute(sprintf('cd %s && git remote add composer %s', escapeshellarg($path), escapeshellarg($initial->getSourceUrl())), $ignoredOutput);
 
         $commandCallable = function($url) use ($ref, $path, $command) {
             return sprintf($command, escapeshellarg($path), escapeshellarg($url), escapeshellarg($ref));
         };
 
         $this->runCommand($commandCallable, $target->getSourceUrl());
-        $this->setPushUrl($target, $path);
     }
 
     /**
@@ -59,7 +61,7 @@ class GitDownloader extends VcsDownloader
      */
     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)) {
             throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
         }

+ 28 - 3
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();
 
         $file = new JsonFile($home.'/config.json');
@@ -84,7 +92,7 @@ class Factory
         }
 
         // Configuration defaults
-        $config = $this->createConfig();
+        $config = static::createConfig();
         $config->merge($localConfig);
 
         $vendorDir = $config->get('vendor-dir');
@@ -151,8 +159,25 @@ class Factory
 
     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)

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

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

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

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

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

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

+ 40 - 11
src/Composer/Installer.php

@@ -27,6 +27,7 @@ use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Package\Locker;
 use Composer\Package\PackageInterface;
+use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Repository\RepositoryInterface;
@@ -76,6 +77,11 @@ class Installer
      */
     protected $eventDispatcher;
 
+    /**
+     * @var AutoloadGenerator
+     */
+    protected $autoloadGenerator;
+
     protected $preferSource = false;
     protected $devMode = false;
     protected $dryRun = false;
@@ -102,8 +108,9 @@ class Installer
      * @param Locker $locker
      * @param InstallationManager $installationManager
      * @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->package = $package;
@@ -112,6 +119,7 @@ class Installer
         $this->locker = $locker;
         $this->installationManager = $installationManager;
         $this->eventDispatcher = $eventDispatcher;
+        $this->autoloadGenerator = $autoloadGenerator;
     }
 
     /**
@@ -128,7 +136,13 @@ class Installer
         }
 
         // 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);
         if ($this->additionalInstalledRepository) {
             $installedRepo->addRepository($this->additionalInstalledRepository);
@@ -152,9 +166,11 @@ class Installer
             }
         }
 
-        // dump suggestions
+        // output suggestions
         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) {
@@ -172,9 +188,8 @@ class Installer
 
             // write autoloader
             $this->io->write('<info>Generating autoload files</info>');
-            $generator = new AutoloadGenerator;
             $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
             $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)
     {
+        // initialize locker to create aliased packages
+        if (!$this->update && $this->locker->isLocked($devMode)) {
+            $lockedPackages = $this->locker->getLockedPackages($devMode);
+        }
+
         // creating repository pool
         $pool = new Pool;
         $pool->addRepository($installedRepo);
@@ -196,6 +216,10 @@ class Installer
         // creating requirements request
         $installFromLock = false;
         $request = new Request($pool);
+
+        $constraint = new VersionConstraint('=', $this->package->getVersion());
+        $request->install($this->package->getName(), $constraint);
+
         if ($this->update) {
             $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>');
             }
 
-            foreach ($this->locker->getLockedPackages($devMode) as $package) {
+            foreach ($lockedPackages as $package) {
                 $version = $package->getVersion();
                 foreach ($aliases as $alias) {
                     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) {
                 $package->setAlias($alias['alias_normalized']);
                 $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 ($repo->findPackages($alias['package'], $alias['version']) as $package) {
                     $package->setAlias($alias['alias_normalized']);
                     $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);
+                    $aliasPackage->setRootPackageAlias(true);
                 }
             }
         }
@@ -399,11 +425,13 @@ class Installer
      * @param IOInterface $io
      * @param Composer $composer
      * @param EventDispatcher $eventDispatcher
+     * @param AutoloadGenerator $autoloadGenerator
      * @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);
+        $autoloadGenerator = $autoloadGenerator ?: new AutoloadGenerator;
 
         return new static(
             $io,
@@ -412,7 +440,8 @@ class Installer
             $composer->getRepositoryManager(),
             $composer->getLocker(),
             $composer->getInstallationManager(),
-            $eventDispatcher
+            $eventDispatcher,
+            $autoloadGenerator
         );
     }
 

+ 21 - 21
src/Composer/Installer/InstallationManager.php

@@ -16,6 +16,7 @@ use Composer\Package\PackageInterface;
 use Composer\Package\AliasPackage;
 use Composer\Repository\RepositoryInterface;
 use Composer\Repository\NotifiableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\DependencyResolver\Operation\InstallOperation;
 use Composer\DependencyResolver\Operation\UpdateOperation;
@@ -96,12 +97,12 @@ class InstallationManager
     /**
      * Checks whether provided package is installed in one of the registered installers.
      *
-     * @param   RepositoryInterface    $repo    repository in which to check
+     * @param   InstalledRepositoryInterface    $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      *
      * @return  Boolean
      */
-    public function isPackageInstalled(RepositoryInterface $repo, PackageInterface $package)
+    public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         return $this->getInstaller($package->getType())->isInstalled($repo, $package);
     }
@@ -126,11 +127,7 @@ class InstallationManager
      */
     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->install($repo, $package);
         $this->notifyInstall($package);
@@ -144,15 +141,8 @@ class InstallationManager
      */
     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();
         $targetType  = $target->getType();
@@ -175,10 +165,7 @@ class InstallationManager
      */
     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->uninstall($repo, $package);
     }
@@ -210,10 +197,23 @@ class InstallationManager
         return getcwd().DIRECTORY_SEPARATOR.$this->vendorPath;
     }
 
-    protected function notifyInstall(PackageInterface $package)
+    private function notifyInstall(PackageInterface $package)
     {
         if ($package->getRepository() instanceof NotifiableRepositoryInterface) {
             $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;
+    }
 }

+ 4 - 4
src/Composer/Installer/InstallerInstaller.php

@@ -15,7 +15,7 @@ namespace Composer\Installer;
 use Composer\IO\IOInterface;
 use Composer\Autoload\AutoloadGenerator;
 use Composer\Downloader\DownloadManager;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Package\PackageInterface;
 
@@ -35,7 +35,7 @@ class InstallerInstaller extends LibraryInstaller
      * @param   DownloadManager             $dm         download manager
      * @param   IOInterface                 $io         io instance
      * @param   InstallationManager         $im         installation manager
-     * @param   array                       $localRepositories array of WritableRepositoryInterface
+     * @param   array                       $localRepositories array of InstalledRepositoryInterface
      */
     public function __construct($vendorDir, $binDir, DownloadManager $dm, IOInterface $io, InstallationManager $im, array $localRepositories)
     {
@@ -54,7 +54,7 @@ class InstallerInstaller extends LibraryInstaller
     /**
      * {@inheritDoc}
      */
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         $extra = $package->getExtra();
         if (empty($extra['class'])) {
@@ -68,7 +68,7 @@ class InstallerInstaller extends LibraryInstaller
     /**
      * {@inheritDoc}
      */
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
         $extra = $target->getExtra();
         if (empty($extra['class'])) {

+ 9 - 9
src/Composer/Installer/InstallerInterface.php

@@ -14,7 +14,7 @@ namespace Composer\Installer;
 
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Package\PackageInterface;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 
 /**
  * Interface for the package installation manager.
@@ -35,39 +35,39 @@ interface InstallerInterface
     /**
      * Checks that provided package is installed.
      *
-     * @param   WritableRepositoryInterface $repo    repository in which to check
+     * @param   InstalledRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      *
      * @return  Boolean
      */
-    function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package);
+    function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Installs specific package.
      *
-     * @param   WritableRepositoryInterface $repo    repository in which to check
+     * @param   InstalledRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      */
-    function install(WritableRepositoryInterface $repo, PackageInterface $package);
+    function install(InstalledRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Updates specific package.
      *
-     * @param   WritableRepositoryInterface $repo    repository in which to check
+     * @param   InstalledRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $initial    already installed package version
      * @param   PackageInterface    $target     updated version
      *
      * @throws  InvalidArgumentException        if $from package is not installed
      */
-    function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);
+    function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);
 
     /**
      * Uninstalls specific package.
      *
-     * @param   WritableRepositoryInterface $repo    repository in which to check
+     * @param   InstalledRepositoryInterface $repo    repository in which to check
      * @param   PackageInterface    $package    package instance
      */
-    function uninstall(WritableRepositoryInterface $repo, PackageInterface $package);
+    function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);
 
     /**
      * Returns the installation path of a package

+ 14 - 11
src/Composer/Installer/LibraryInstaller.php

@@ -14,7 +14,7 @@ namespace Composer\Installer;
 
 use Composer\IO\IOInterface;
 use Composer\Downloader\DownloadManager;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Package\PackageInterface;
 use Composer\Util\Filesystem;
@@ -65,7 +65,7 @@ class LibraryInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         return $repo->hasPackage($package) && is_readable($this->getInstallPath($package));
     }
@@ -73,7 +73,7 @@ class LibraryInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         $this->initializeVendorDir();
         $downloadPath = $this->getInstallPath($package);
@@ -93,7 +93,7 @@ class LibraryInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
         if (!$repo->hasPackage($initial)) {
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
@@ -114,7 +114,7 @@ class LibraryInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         if (!$repo->hasPackage($package)) {
             // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)
@@ -152,7 +152,7 @@ class LibraryInstaller implements InstallerInterface
                     // likely leftover from a previous install, make sure
                     // that the target is still executable in case this
                     // 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');
                 continue;
@@ -163,21 +163,24 @@ class LibraryInstaller implements InstallerInterface
                 // add unixy support for cygwin and similar environments
                 if ('.bat' !== substr($bin, -4)) {
                     file_put_contents($link, $this->generateUnixyProxyCode($bin, $link));
-                    chmod($link, 0755);
+                    chmod($link, 0777 & ~umask());
                     $link .= '.bat';
                 }
                 file_put_contents($link, $this->generateWindowsProxyCode($bin, $link));
             } else {
+                $cwd = getcwd();
                 try {
                     // under linux symlinks are not always supported for example
                     // 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) {
                     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()) {
             return;
         }
-        foreach ($package->getBinaries() as $bin => $os) {
+        foreach ($package->getBinaries() as $bin) {
             $link = $this->binDir.'/'.basename($bin);
             if (!file_exists($link)) {
                 continue;

+ 5 - 5
src/Composer/Installer/MetapackageInstaller.php

@@ -12,7 +12,7 @@
 
 namespace Composer\Installer;
 
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 use Composer\Package\PackageInterface;
 
 /**
@@ -33,7 +33,7 @@ class MetapackageInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         return $repo->hasPackage($package);
     }
@@ -41,7 +41,7 @@ class MetapackageInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         $repo->addPackage(clone $package);
     }
@@ -49,7 +49,7 @@ class MetapackageInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
         if (!$repo->hasPackage($initial)) {
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
@@ -62,7 +62,7 @@ class MetapackageInstaller implements InstallerInterface
     /**
      * {@inheritDoc}
      */
-    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package)
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         if (!$repo->hasPackage($package)) {
             // TODO throw exception again here, when update is fixed and we don't have to remove+install (see #125)

+ 9 - 21
src/Composer/Installer/ProjectInstaller.php

@@ -15,6 +15,7 @@ namespace Composer\Installer;
 use Composer\DependencyResolver\Operation\OperationInterface;
 use Composer\Package\PackageInterface;
 use Composer\Downloader\DownloadManager;
+use Composer\Repository\InstalledRepositoryInterface;
 
 /**
  * Project Installer is used to install a single package into a directory as
@@ -45,23 +46,17 @@ class ProjectInstaller implements InstallerInterface
     }
 
     /**
-     * Checks that provided package is installed.
-     *
-     * @param   PackageInterface    $package    package instance
-     *
-     * @return  Boolean
+     * {@inheritDoc}
      */
-    public function isInstalled(PackageInterface $package)
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         return false;
     }
 
     /**
-     * Installs specific package.
-     *
-     * @param   PackageInterface    $package    package instance
+     * {@inheritDoc}
      */
-    public function install(PackageInterface $package)
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         $installPath = $this->installPath;
         if (file_exists($installPath)) {
@@ -75,24 +70,17 @@ class ProjectInstaller implements InstallerInterface
     }
 
     /**
-     * Updates specific package.
-     *
-     * @param   PackageInterface    $initial    already installed package version
-     * @param   PackageInterface    $target     updated version
-     *
-     * @throws  InvalidArgumentException        if $from package is not installed
+     * {@inheritDoc}
      */
-    public function update(PackageInterface $initial, PackageInterface $target)
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
     {
         throw new \InvalidArgumentException("not supported");
     }
 
     /**
-     * Uninstalls specific package.
-     *
-     * @param   PackageInterface    $package    package instance
+     * {@inheritDoc}
      */
-    public function uninstall(PackageInterface $package)
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
     {
         throw new \InvalidArgumentException("not supported");
     }

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

@@ -237,7 +237,7 @@ class JsonFile
                     }
                 } else {
                     // 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 $dev;
     protected $aliasOf;
+    protected $rootPackageAlias = false;
 
     protected $requires;
     protected $conflicts;
@@ -146,6 +147,27 @@ class AliasPackage extends BasePackage
         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}
      */
@@ -222,6 +244,14 @@ class AliasPackage extends BasePackage
     {
         return $this->aliasOf->getScripts();
     }
+    public function setAliases(array $aliases)
+    {
+        return $this->aliasOf->setAliases($aliases);
+    }
+    public function getAliases()
+    {
+        return $this->aliasOf->getAliases();
+    }
     public function getLicense()
     {
         return $this->aliasOf->getLicense();

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

@@ -58,7 +58,10 @@ class ArrayLoader
             $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) {
                 $config['bin'][$key]= ltrim($bin, '/');
             }
@@ -166,6 +169,11 @@ class ArrayLoader
         }
 
         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']);
         }
 

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

@@ -14,6 +14,8 @@ namespace Composer\Package\Loader;
 
 use Composer\Package\Version\VersionParser;
 use Composer\Repository\RepositoryManager;
+use Composer\Util\ProcessExecutor;
+use Composer\Package\AliasPackage;
 
 /**
  * ArrayLoader built for the sole purpose of loading the root package
@@ -25,10 +27,12 @@ use Composer\Repository\RepositoryManager;
 class RootPackageLoader extends ArrayLoader
 {
     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->process = $process ?: new ProcessExecutor();
         parent::__construct($parser);
     }
 
@@ -38,7 +42,20 @@ class RootPackageLoader extends ArrayLoader
             $config['name'] = '__root__';
         }
         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);
@@ -76,6 +93,16 @@ class RootPackageLoader extends ArrayLoader
             $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;
     }
 }

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

@@ -90,12 +90,22 @@ class Locker
 
         foreach ($lockedPackages as $info) {
             $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);
 
+            // try to find the package in any repo
             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']);
-                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;
 
+    public function __construct(array $packages = array())
+    {
+        foreach ($packages as $package) {
+            $this->addPackage($package);
+        }
+    }
+
     /**
      * {@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'] = 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']);
         }
 
@@ -70,7 +70,7 @@ class ComposerRepository extends ArrayRepository implements NotifiableRepository
             array(
                 'method'  => 'POST',
                 'header'  => 'Content-type: application/x-www-form-urlencoded',
-                'content' => http_build_query($params),
+                'content' => http_build_query($params, '', '&'),
                 'timeout' => 3,
             )
         );

+ 1 - 1
src/Composer/Repository/InstalledRepositoryInterface.php

@@ -21,6 +21,6 @@ use Composer\Package\PackageInterface;
  *
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
-interface InstalledRepositoryInterface
+interface InstalledRepositoryInterface extends WritableRepositoryInterface
 {
 }

+ 21 - 11
src/Composer/Repository/PearRepository.php

@@ -28,6 +28,7 @@ class PearRepository extends ArrayRepository
     private static $channelNames = array();
 
     private $url;
+    private $baseUrl;
     private $channel;
     private $io;
     private $rfs;
@@ -38,7 +39,7 @@ class PearRepository extends ArrayRepository
             $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']);
         }
 
@@ -68,8 +69,13 @@ class PearRepository extends ArrayRepository
             $loader = new ArrayLoader();
             foreach ($packages as $data) {
                 foreach ($data['versions'] as $rev) {
-                    $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name'];
+                    if (strpos($rev['name'], 'pear-'.$this->channel) !== 0) {
+                        $rev['name'] = 'pear-'.$this->channel.'/'.$rev['name'];
+                    }
                     $this->addPackage($loader->load($rev));
+                    if ($this->io->isVerbose()) {
+                        $this->io->write('Loaded '.$rev['name'].' '.$rev['version']);
+                    }
                 }
             }
 
@@ -87,26 +93,31 @@ class PearRepository extends ArrayRepository
             $this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue
                                     ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue;
         }
+        if (!$this->baseUrl) {
+            $this->baseUrl = $channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue
+                                    ? trim($channelXML->getElementsByTagName("baseurl")->item(0)->nodeValue, '/')
+                                    : $this->url . '/rest';
+        }
 
         self::$channelNames[$channelXML->getElementsByTagName("name")->item(0)->nodeValue] = $this->channel;
     }
 
     protected function fetchFromServer()
     {
-        $categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml");
+        $categoryXML = $this->requestXml($this->baseUrl . "/c/categories.xml");
         $categories = $categoryXML->getElementsByTagName("c");
 
         foreach ($categories as $category) {
-            $link = '/' . ltrim($category->getAttribute("xlink:href"), '/');
+            $link = $this->baseUrl . '/c/' . str_replace(' ', '+', $category->nodeValue);
             try {
-                $packagesLink = str_replace("info.xml", "packagesinfo.xml", $link);
-                $this->fetchPear2Packages($this->url . $packagesLink);
+                $packagesLink = $link . "/packagesinfo.xml";
+                $this->fetchPear2Packages($packagesLink);
             } catch (TransportException $e) {
                 if (false === strpos($e->getMessage(), '404')) {
                     throw $e;
                 }
-                $categoryLink = str_replace("info.xml", "packages.xml", $link);
-                $this->fetchPearPackages($this->url . $categoryLink);
+                $categoryLink = $link . "/packages.xml";
+                $this->fetchPearPackages($categoryLink);
             }
 
         }
@@ -126,8 +137,7 @@ class PearRepository extends ArrayRepository
             $packageName = $package->nodeValue;
             $fullName = 'pear-'.$this->channel.'/'.$packageName;
 
-            $packageLink = $package->getAttribute('xlink:href');
-            $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink);
+            $releaseLink = $this->baseUrl . "/r/" . $packageName;
             $allReleasesLink = $releaseLink . "/allreleases2.xml";
 
             try {
@@ -357,7 +367,7 @@ class PearRepository extends ArrayRepository
             throw new \UnexpectedValueException('The PEAR channel at '.$url.' did not respond.');
         }
         $dom = new \DOMDocument('1.0', 'UTF-8');
-        $dom->loadXML($content);
+        $dom->loadXML($content, LIBXML_NOERROR);
 
         return $dom;
     }

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

@@ -27,20 +27,14 @@ class GitBitbucketDriver extends VcsDriver implements VcsDriverInterface
     protected $rootIdentifier;
     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}
      */
     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 $infoCache = array();
 
-    public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
-    {
-        parent::__construct($url, $io, $process);
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -41,11 +36,13 @@ class GitDriver extends VcsDriver
         if (static::isLocalUrl($this->url)) {
             $this->repoDir = str_replace('file://', '', $this->url);
         } 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
             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 {
                 // clean up directory and do a fresh clone into it
                 $fs = new Filesystem();

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

@@ -39,27 +39,14 @@ class GitHubDriver extends VcsDriver
     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->repository = $match[2];
 
-        parent::__construct($url, $io, $process, $remoteFilesystem);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function initialize()
-    {
         $this->fetchRootIdentifier();
     }
 
@@ -248,6 +235,7 @@ class GitHubDriver extends VcsDriver
                             $this->gitDriver = new GitDriver(
                                 $this->generateSshUrl(),
                                 $this->io,
+                                $this->config,
                                 $this->process,
                                 $this->remoteFilesystem
                             );

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

@@ -27,20 +27,14 @@ class HgBitbucketDriver extends VcsDriver
     protected $rootIdentifier;
     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}
      */
     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 $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}
      */
     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)) {
-            $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 {
-            $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();

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

@@ -33,25 +33,7 @@ class SvnDriver extends VcsDriver
     /**
      * @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
@@ -64,6 +46,10 @@ class SvnDriver extends VcsDriver
      */
     protected function execute($command, $url)
     {
+        if (null === $this->util) {
+            $this->util = new SvnUtil($this->baseUrl, $this->io, $this->process);
+        }
+
         try {
             return $this->util->execute($command, $url);
         } catch (\RuntimeException $e) {
@@ -78,6 +64,12 @@ class SvnDriver extends VcsDriver
      */
     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->getTags();
     }

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

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

+ 5 - 3
src/Composer/Repository/VcsRepository.php

@@ -30,6 +30,7 @@ class VcsRepository extends ArrayRepository
     protected $packageName;
     protected $verbose;
     protected $io;
+    protected $config;
     protected $versionParser;
     protected $type;
 
@@ -48,20 +49,21 @@ class VcsRepository extends ArrayRepository
         $this->io = $io;
         $this->type = isset($repoConfig['type']) ? $repoConfig['type'] : 'vcs';
         $this->verbose = $io->isVerbose();
+        $this->config = $config;
     }
 
     public function getDriver()
     {
         if (isset($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();
             return $driver;
         }
 
         foreach ($this->drivers as $driver) {
             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();
                 return $driver;
             }
@@ -69,7 +71,7 @@ class VcsRepository extends ArrayRepository
 
         foreach ($this->drivers as $driver) {
             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();
                 return $driver;
             }

+ 5 - 1
src/Composer/Util/StreamContextFactory.php

@@ -46,7 +46,11 @@ final class StreamContextFactory
             }
             
             // http(s):// is not supported in proxy
-            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);
+            if ('http://' == substr($proxy, 0, 7)) {
+                $proxy = 'tcp://' . rtrim(substr($proxy, 7), '/') . (parse_url($proxy, PHP_URL_PORT) ? '' : ':80');
+            } else if ('https://' == substr($proxy, 0, 8)) {
+                $proxy = 'ssl://' . rtrim(substr($proxy, 8), '/') . (parse_url($proxy, PHP_URL_PORT) ? '' : ':443');
+            }
 
             if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
                 throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');

+ 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()
     {
         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)) {
             $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);
@@ -78,22 +78,22 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => array('src/', 'lib/')),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
 
         $this->repository->expects($this->once())
             ->method('getPackages')
             ->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->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()
@@ -103,22 +103,22 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
 
         $this->repository->expects($this->once())
             ->method('getPackages')
             ->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->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()
@@ -126,7 +126,7 @@ class AutoloadGeneratorTest extends TestCase
         $package = new MemoryPackage('a', '1.0', '1.0');
         $package->setAutoload(array(
             'psr-0' => array('Main' => 'src/', 'Lala' => 'src/'),
-            'classmap' => array('.composersrc/'),
+            'classmap' => array('composersrc/'),
         ));
 
         $this->repository->expects($this->once())
@@ -134,11 +134,27 @@ class AutoloadGeneratorTest extends TestCase
             ->will($this->returnValue(array()));
 
         $this->vendorDir .= '/subdir';
-        mkdir($this->vendorDir.'/.composer', 0777, true);
+        mkdir($this->vendorDir.'/composer', 0777, true);
         $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()
@@ -155,10 +171,10 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->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()
@@ -175,7 +191,7 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->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.'/b/b/src', 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/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(
             array(
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/src/b.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()
@@ -212,7 +228,7 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->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.'/b/b', 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.'/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(
             array(
                 'ClassMapFoo' => $this->workingDir.'/composer-test-autoload/a/a/src/a.php',
                 'ClassMapBar' => $this->workingDir.'/composer-test-autoload/b/b/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()
@@ -248,9 +264,9 @@ class AutoloadGeneratorTest extends TestCase
             ->method('getPackages')
             ->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()
@@ -264,23 +280,29 @@ class AutoloadGeneratorTest extends TestCase
         $b = new MemoryPackage("b/b", "1.0", "1.0");
         $b->setIncludePaths(array("library"));
 
+        $c = new MemoryPackage("c", "1.0", "1.0");
+        $c->setIncludePaths(array("library"));
+
         $packages[] = $a;
         $packages[] = $b;
+        $packages[] = $c;
 
         $this->repository->expects($this->once())
             ->method("getPackages")
             ->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(
             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")
             ->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();
 
-        require($this->vendorDir."/.composer/autoload.php");
+        require($this->vendorDir."/autoload.php");
 
         $this->assertEquals(
-            $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib/",
+            $oldIncludePath.PATH_SEPARATOR.$this->vendorDir."/a/a/lib",
             get_include_path()
         );
 
@@ -326,20 +348,20 @@ class AutoloadGeneratorTest extends TestCase
             ->method("getPackages")
             ->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)
     {
-        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')

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

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

+ 18 - 10
tests/Composer/Test/Downloader/GitDownloaderTest.php

@@ -50,7 +50,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('https://example.com/composer/composer'));
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
 
-        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://example.com/composer/composer'");
         $processExecutor->expects($this->once())
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -71,19 +71,19 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('https://github.com/composer/composer'));
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');
 
-        $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitCommand = $this->getCmd("git clone 'git://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'git://github.com/composer/composer'");
         $processExecutor->expects($this->at(0))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(1));
 
-        $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitCommand = $this->getCmd("git clone 'https://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://github.com/composer/composer'");
         $processExecutor->expects($this->at(1))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
             ->will($this->returnValue(1));
 
-        $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitCommand = $this->getCmd("git clone 'http://github.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'http://github.com/composer/composer'");
         $processExecutor->expects($this->at(2))
             ->method('execute')
             ->with($this->equalTo($expectedGitCommand))
@@ -104,7 +104,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
      */
     public function testDownloadThrowsRuntimeExceptionIfGitCommandFails()
     {
-        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref'");
+        $expectedGitCommand = $this->getCmd("git clone 'https://example.com/composer/composer' 'composerPath' && cd 'composerPath' && git checkout 'ref' && git reset --hard 'ref' && git remote add composer 'https://example.com/composer/composer'");
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
             ->method('getSourceReference')
@@ -139,8 +139,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
     public function testUpdate()
     {
-        $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch origin && git fetch --tags origin && git checkout 'ref' && git reset --hard 'ref'");
-        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
+        $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 --untracked-files=no");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
@@ -155,6 +155,10 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->with($this->equalTo($expectedGitResetCommand))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
+            ->method('execute')
+            ->with($this->equalTo($this->getCmd("cd 'composerPath' && git remote add composer 'https://github.com/composer/composer'")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(2))
             ->method('execute')
             ->with($this->equalTo($expectedGitUpdateCommand))
             ->will($this->returnValue(0));
@@ -168,8 +172,8 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
      */
     public function testUpdateThrowsRuntimeExceptionIfGitCommandFails()
     {
-        $expectedGitUpdateCommand = $this->getCmd("cd 'composerPath' && git remote set-url origin 'git://github.com/composer/composer' && git fetch origin && git fetch --tags origin && git checkout 'ref' && git reset --hard 'ref'");
-        $expectedGitResetCommand = $this->getCmd("cd 'composerPath' && git status --porcelain");
+        $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 --untracked-files=no");
 
         $packageMock = $this->getMock('Composer\Package\PackageInterface');
         $packageMock->expects($this->any())
@@ -184,6 +188,10 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
             ->with($this->equalTo($expectedGitResetCommand))
             ->will($this->returnValue(0));
         $processExecutor->expects($this->at(1))
+            ->method('execute')
+            ->with($this->equalTo($this->getCmd("cd 'composerPath' && git remote add composer 'https://github.com/composer/composer'")))
+            ->will($this->returnValue(0));
+        $processExecutor->expects($this->at(2))
             ->method('execute')
             ->with($this->equalTo($expectedGitUpdateCommand))
             ->will($this->returnValue(1));
@@ -194,7 +202,7 @@ class GitDownloaderTest extends \PHPUnit_Framework_TestCase
 
     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');
         $processExecutor = $this->getMock('Composer\Util\ProcessExecutor');

+ 5 - 5
tests/Composer/Test/Installer/Fixtures/installer-v1/Installer/Custom.php

@@ -4,16 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 
 class Custom implements InstallerInterface
 {
     public $version = 'installer-v1';
 
     public function supports($packageType) {}
-    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 5 - 5
tests/Composer/Test/Installer/Fixtures/installer-v2/Installer/Custom2.php

@@ -4,16 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 
 class Custom2 implements InstallerInterface
 {
     public $version = 'installer-v2';
 
     public function supports($packageType) {}
-    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 5 - 5
tests/Composer/Test/Installer/Fixtures/installer-v3/Installer/Custom2.php

@@ -4,16 +4,16 @@ namespace Installer;
 
 use Composer\Installer\InstallerInterface;
 use Composer\Package\PackageInterface;
-use Composer\Repository\WritableRepositoryInterface;
+use Composer\Repository\InstalledRepositoryInterface;
 
 class Custom2 implements InstallerInterface
 {
     public $version = 'installer-v3';
 
     public function supports($packageType) {}
-    public function isInstalled(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function install(WritableRepositoryInterface $repo, PackageInterface $package) {}
-    public function update(WritableRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
-    public function uninstall(WritableRepositoryInterface $repo, PackageInterface $package) {}
+    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function install(InstalledRepositoryInterface $repo, PackageInterface $package) {}
+    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) {}
+    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) {}
     public function getInstallPath(PackageInterface $package) {}
 }

+ 1 - 1
tests/Composer/Test/Installer/InstallationManagerTest.php

@@ -21,7 +21,7 @@ class InstallationManagerTest extends \PHPUnit_Framework_TestCase
 {
     public function setUp()
     {
-        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
+        $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
     }
 
     public function testVendorDirOutsideTheWorkingDir()

+ 1 - 1
tests/Composer/Test/Installer/InstallerInstallerTest.php

@@ -34,7 +34,7 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
+        $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
 
         $this->io = $this->getMock('Composer\IO\IOInterface');
     }

+ 1 - 1
tests/Composer/Test/Installer/LibraryInstallerTest.php

@@ -40,7 +40,7 @@ class LibraryInstallerTest extends TestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
+        $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
 
         $this->io = $this->getMock('Composer\IO\IOInterface');
     }

+ 1 - 1
tests/Composer/Test/Installer/MetapackageInstallerTest.php

@@ -22,7 +22,7 @@ class MetapackageInstallerTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->repository = $this->getMock('Composer\Repository\WritableRepositoryInterface');
+        $this->repository = $this->getMock('Composer\Repository\InstalledRepositoryInterface');
 
         $this->io = $this->getMock('Composer\IO\IOInterface');
 

+ 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);
     }
 
+    public function testFormatEmptyArray()
+    {
+        $data = array('test' => array(), 'test2' => new \stdClass);
+        $json = '{
+    "test": [
+
+    ],
+    "test2": {
+
+    }
+}';
+        $this->assertJsonFormat($json, $data);
+    }
+
     public function testEscape()
     {
         $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',
             'license' => array('MIT', 'GPLv3'),
             '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(
                 '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\Repository\Vcs\GitHubDriver;
 use Composer\Util\Filesystem;
+use Composer\Config;
 
 /**
  * @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))
             ->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();
         $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))
             ->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();
         $this->setAttribute($gitHubDriver, 'tags', array($identifier => $sha));
 
@@ -171,7 +172,14 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
 
         // clean local clone if present
         $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))
             ->method('execute')
@@ -202,7 +210,7 @@ class GitHubDriverTest extends \PHPUnit_Framework_TestCase
             ->method('splitLines')
             ->will($this->returnValue(array('* test_master')));
 
-        $gitHubDriver = new GitHubDriver($repoUrl, $io, $process, $remoteFilesystem);
+        $gitHubDriver = new GitHubDriver($repoUrl, $io, $config, $process, $remoteFilesystem);
         $gitHubDriver->initialize();
 
         $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\IO\NullIO;
+use Composer\Config;
 
 class SvnDriverTest extends \PHPUnit_Framework_TestCase
 {
@@ -39,8 +40,8 @@ class SvnDriverTest extends \PHPUnit_Framework_TestCase
             ->method('getErrorOutput')
             ->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)

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

@@ -19,6 +19,7 @@ use Composer\Repository\Vcs\GitDriver;
 use Composer\Util\Filesystem;
 use Composer\Util\ProcessExecutor;
 use Composer\IO\NullIO;
+use Composer\Config;
 
 class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
 {
@@ -123,7 +124,7 @@ class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
             '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();
         $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
 {
-    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)
     {
         return new VersionConstraint(
             $operator,
-            self::$versionParser->normalize($version)
+            self::getVersionParser()->normalize($version)
         );
     }
 
     protected function getPackage($name, $version)
     {
-        $normVersion = self::$versionParser->normalize($version);
+        $normVersion = self::getVersionParser()->normalize($version);
+
         return new MemoryPackage($name, $normVersion, $version);
     }
 

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

@@ -57,30 +57,48 @@ class StreamContextFactoryTest extends \PHPUnit_Framework_TestCase
 
     public function testHttpProxy()
     {
-        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:1234';
-        $_SERVER['HTTP_PROXY'] = 'http://proxyserver';
+        $_SERVER['http_proxy'] = 'http://username:password@proxyserver.net:3128/';
+        $_SERVER['HTTP_PROXY'] = 'http://proxyserver/';
 
         $context = StreamContextFactory::getContext(array('http' => array('method' => 'GET')));
         $options = stream_context_get_options($context);
 
         $this->assertEquals(array('http' => array(
-            'proxy' => 'tcp://proxyserver.net:1234',
+            'proxy' => 'tcp://proxyserver.net:3128',
             'request_fulluri' => true,
             'method' => 'GET',
             'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n"
         )), $options);
     }
 
-    public function testSSLProxy()
+    public function testHttpProxyWithoutPort()
     {
-        $_SERVER['http_proxy'] = 'https://proxyserver';
+        $_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(
+            'proxy' => 'tcp://proxyserver.net:80',
+            'request_fulluri' => true,
+            'method' => 'GET',
+            'header' => "Proxy-Authorization: Basic " . base64_encode('username:password') . "\r\n"
+        )), $options);
+    }
+
+    /**
+     * @dataProvider dataSSLProxy
+     */
+    public function testSSLProxy($expected, $proxy)
+    {
+        $_SERVER['http_proxy'] = $proxy;
 
         if (extension_loaded('openssl')) {
             $context = StreamContextFactory::getContext();
             $options = stream_context_get_options($context);
 
-            $this->assertSame(array('http' => array(
-                'proxy' => 'ssl://proxyserver',
+						$this->assertEquals(array('http' => array(
+                'proxy' => $expected,
                 'request_fulluri' => true,
             )), $options);
         } else {
@@ -92,4 +110,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'),
+        );
+    }
 }