Эх сурвалжийг харах

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

digitalkaoz 13 жил өмнө
parent
commit
1cf92eb6c8
46 өөрчлөгдсөн 2110 нэмэгдсэн , 276 устгасан
  1. 4 0
      README.md
  2. 3 3
      composer.json
  3. 11 8
      composer.lock
  4. 65 0
      doc/00-intro.md
  5. 189 0
      doc/01-basic-usage.md
  6. 168 0
      doc/02-libraries.md
  7. 139 0
      doc/03-cli.md
  8. 417 0
      doc/04-schema.md
  9. 263 0
      doc/05-repositories.md
  10. 28 0
      doc/06-community.md
  11. 0 0
      doc/articles/packagist-update-schedule.md
  12. 0 0
      doc/articles/scripts.md
  13. 0 0
      doc/articles/vendor-bins.md
  14. 0 0
      doc/dev/DefaultPolicy.md
  15. 67 0
      res/composer-schema.json
  16. 9 2
      src/Composer/Command/DependsCommand.php
  17. 45 29
      src/Composer/Command/InstallCommand.php
  18. 0 5
      src/Composer/DependencyResolver/DefaultPolicy.php
  19. 0 1
      src/Composer/DependencyResolver/PolicyInterface.php
  20. 5 0
      src/Composer/DependencyResolver/Request.php
  21. 0 2
      src/Composer/DependencyResolver/RuleSet.php
  22. 89 55
      src/Composer/DependencyResolver/Solver.php
  23. 65 0
      src/Composer/DependencyResolver/SolverProblemsException.php
  24. 4 4
      src/Composer/Downloader/DownloadManager.php
  25. 9 1
      src/Composer/Factory.php
  26. 13 7
      src/Composer/Installer/LibraryInstaller.php
  27. 2 0
      src/Composer/Package/Loader/ArrayLoader.php
  28. 8 5
      src/Composer/Package/Loader/RootPackageLoader.php
  29. 20 6
      src/Composer/Package/Locker.php
  30. 11 0
      src/Composer/Package/MemoryPackage.php
  31. 7 0
      src/Composer/Package/PackageInterface.php
  32. 8 3
      src/Composer/Package/Version/VersionParser.php
  33. 1 1
      src/Composer/Repository/PackageRepository.php
  34. 14 2
      src/Composer/Repository/PearRepository.php
  35. 36 10
      src/Composer/Repository/Vcs/GitDriver.php
  36. 5 0
      src/Composer/Repository/Vcs/VcsDriver.php
  37. 27 31
      src/Composer/Repository/VcsRepository.php
  38. 12 0
      tests/Composer/Test/DependencyResolver/RequestTest.php
  39. 3 3
      tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php
  40. 9 10
      tests/Composer/Test/DependencyResolver/RuleSetTest.php
  41. 155 42
      tests/Composer/Test/DependencyResolver/SolverTest.php
  42. 4 4
      tests/Composer/Test/Installer/InstallerInstallerTest.php
  43. 2 3
      tests/Composer/Test/Installer/LibraryInstallerTest.php
  44. 10 5
      tests/Composer/Test/Package/Version/VersionParserTest.php
  45. 140 0
      tests/Composer/Test/Repository/VcsRepositoryTest.php
  46. 43 34
      tests/Composer/Test/TestCase.php

+ 4 - 0
README.md

@@ -97,6 +97,10 @@ merged. This is to ensure proper review of all the code.
 
 Fork the project, create a feature branch, and send us a pull request.
 
+To ensure a consistent code base, you should make sure the code follows
+the [Coding Standards](http://symfony.com/doc/2.0/contributing/code/standards.html)
+which we borrowed from Symfony.
+
 If you would like to help take a look at the [list of issues](http://github.com/composer/composer/issues).
 
 Community

+ 3 - 3
composer.json

@@ -19,9 +19,9 @@
     ],
     "require": {
         "php": ">=5.3.0",
-        "symfony/console": "2.1.0-dev",
-        "symfony/finder": ">2.0,<2.2-dev",
-        "symfony/process": ">2.0,<2.2-dev"
+        "symfony/console": "dev-master",
+        "symfony/finder": "dev-master",
+        "symfony/process": "dev-master"
     },
     "recommend": {
         "ext-zip": "*"

+ 11 - 8
composer.lock

@@ -1,17 +1,20 @@
 {
-    "hash": "9c243b2c15fdc7c3e35c5200d704ba53",
+    "hash": "4ba2fad397e186b6bc453b4417c2ab00",
     "packages": [
         {
-            "package": "symfony\/process",
-            "version": "2.1.0-dev"
+            "package": "symfony/console",
+            "version": "dev-master",
+            "source-reference": "75ca31776bd98ad427f759cbe8a62400e40c73a1"
         },
         {
-            "package": "symfony\/finder",
-            "version": "2.1.0-dev"
+            "package": "symfony/finder",
+            "version": "dev-master",
+            "source-reference": "dd56fc9f1f0baa006d7491d5c17eb3e2dd8a066c"
         },
         {
-            "package": "symfony\/console",
-            "version": "2.1.0-dev"
+            "package": "symfony/process",
+            "version": "dev-master",
+            "source-reference": "f381eeee3733ca0fd374491fab56dce0f3ca8e34"
         }
     ]
-}
+}

+ 65 - 0
doc/00-intro.md

@@ -0,0 +1,65 @@
+# Introduction
+
+Composer is a tool for dependency management in PHP. It allows you to declare
+the dependencies of your project and will install these dependencies for you.
+
+## Dependency management
+
+One important distinction to make is that composer is not a package manager. It
+deals with packages, but it manages them on a per-project basis. By default it
+will never install anything globally. Thus, it is a dependency manager.
+
+This idea is not new by any means. Composer is strongly inspired by
+node's [npm](http://npmjs.org/) and ruby's [bundler](http://gembundler.com/).
+But there has not been such a tool for PHP so far.
+
+The problem that composer solves is the following. You have a project that
+depends on a number of libraries. Some of those libraries have dependencies of
+their own. You declare the things you depend on. Composer will then go ahead
+and find out which versions of which packages need to be installed, and
+install them.
+
+## Declaring dependencies
+
+Let's say you are creating a project, and you need a library that does logging.
+You decide to use [monolog](https://github.com/Seldaek/monolog). In order to
+add it to your project, all you need to do is create a `composer.json` file
+which describes the project's dependencies.
+
+```json
+{
+    "require": {
+        "monolog/monolog": "1.0.*"
+    }
+}
+```
+
+We are simply stating that our project requires the `monolog/monolog` package,
+any version beginning with `1.0`.
+
+## Installation
+
+To actually get it, we need to do two things. The first one is installing
+composer:
+
+    $ curl -s http://getcomposer.org/installer | php
+
+This will just check a few PHP settings and then download `composer.phar` to
+your working directory. This is the composer binary.
+
+After that we run the command for installing all dependencies:
+
+    $ php composer.phar install
+
+This will download monolog and dump it into `vendor/monolog/monolog`.
+
+## Autoloading
+
+After this you can just add the following line to your bootstrap code to get
+autoloading:
+
+```php
+require 'vendor/.composer/autoload.php';
+```
+
+That's all it takes to have a basic setup.

+ 189 - 0
doc/01-basic-usage.md

@@ -0,0 +1,189 @@
+# Basic usage
+
+## Installation
+
+To install composer, simply run this command on the command line:
+
+    $ curl -s http://getcomposer.org/installer | php
+
+This will perform some checks on your environment to make sure you can
+actually run it.
+
+This will download `composer.phar` and place it in your working directory.
+`composer.phar` is the composer binary. It is a PHAR (PHP archive), which
+is an archive format for PHP which can be run on the command line, amongst
+other things.
+
+You can place this file anywhere you wish. If you put it in your `PATH`,
+you can access it globally. On unixy systems you can even make it
+executable and invoke it without `php`.
+
+To check if composer is working, just run the PHAR through `php`:
+
+    $ php composer.phar
+
+This should give you a list of available commands.
+
+> **Note:** You can also perform the checks only without downloading composer
+> by using the `--check` option. For more information, just use `--help`.
+>
+>     $ curl -s http://getcomposer.org/installer | php -- --help
+
+## Project setup
+
+To start using composer in your project, all you need is a `composer.json`
+file. This file describes the dependencies of your project and may contain
+other metadata as well.
+
+The [JSON format](http://json.org/) is quite easy to write. It allows you to
+define nested structures.
+
+The first (and often only) thing you specify in `composer.json` is the
+`require` key. You're simply telling composer which packages your project
+depends on.
+
+```json
+{
+    "require": {
+        "monolog/monolog": "1.0.*"
+    }
+}
+```
+
+As you can see, `require` takes an object that maps package names to versions.
+
+## Package names
+
+The package name consists of a vendor name and the project's name. Often these
+will be identical. The vendor name exists to prevent naming clashes. It allows
+two different people to create a library named `json`, which would then just be
+named `igorw/json` and `seldaek/json`.
+
+Here we are requiring `monolog/monolog`, so the vendor name is the same as the
+project's name. For projects with a unique name this is recommended. It also
+allows adding more related projects under the same namespace later on. If you
+are maintaining a library, this would make it really easy to split it up into
+smaller decoupled parts.
+
+## Package versions
+
+We are also requiring the version `1.0.*` of monolog. This means any version
+in the `1.0` development branch. It would match `1.0.0`, `1.0.2` and `1.0.20`.
+
+Version constraints can be specified in a few different ways.
+
+* **Exact version:** You can specify the exact version of a package, for
+  example `1.0.2`. This is not used very often, but can be useful.
+
+* **Range:** By using comparison operators you can specify ranges of valid
+  versions. Valid operators are `>`, `>=`, `<`, `<=`. An example range would be
+  `>=1.0`. You can define multiple of these, separated by comma:   `>=1.0,<2.0`.
+
+* **Wildcard:** You can specify a pattern with a `*` wildcard. `1.0.*` is the
+  equivalent of `>=1.0,<1.1-dev`.
+
+## Installing dependencies
+
+To fetch the defined dependencies into the local project, you simply run the
+`install` command of `composer.phar`.
+
+    $ php composer.phar install
+
+This will find the latest version of `monolog/monolog` that matches the
+supplied version constraint and download it into the the `vendor` directory.
+It's a convention to put third party code into a directory named `vendor`.
+In case of monolog it will put it into `vendor/monolog/monolog`.
+
+**Tip:** If you are using git for your project, you probably want to add
+`vendor` into your `.gitignore`. You really don't want to add all of that
+code to your repository.
+
+Another thing that the `install` command does is it adds a `composer.lock`
+file into your project root.
+
+## Lock file
+
+After installing the dependencies, composer writes the list of the exact
+versions it installed into a `composer.lock` file. This locks the project
+to those specific versions.
+
+**Commit your project's `composer.lock` into version control.**
+
+The reason is that anyone who sets up the project should get the same version.
+The `install` command will check if a lock file is present. If it is, it will
+use the versions specified there. If not, it will resolve the dependencies and
+create a lock file.
+
+If any of the dependencies gets a new version, you can update to that version
+by using the `update` command. This will fetch the latest matching versions and
+also update the lock file.
+
+    $ php composer.phar update
+
+## Packagist
+
+[Packagist](http://packagist.org/) is the main composer repository. A composer
+repository is basically a package source. A place where you can get packages
+from. Packagist aims to be the central repository that everybody uses. This
+means that you can automatically `require` any package that is available
+there.
+
+If you go to the [packagist website](http://packagist.org/) (packagist.org),
+you can browse and search for packages.
+
+Any open source project using composer should publish their packages on
+packagist.
+
+## Autoloading
+
+For libraries that follow the [PSR-0](https://github.com/php-fig/fig-
+standards/blob/master/accepted/PSR-0.md) naming standard, composer generates a
+`vendor/.composer/autoload.php` file for autoloading. You can simply include
+this file and you will get autoloading for free.
+
+```php
+require 'vendor/.composer/autoload.php';
+```
+
+This makes it really easy to use third party code, because you really just
+have to add one line to `composer.json` and run `install`. For monolog, it
+means that we can just start using classes from it, and they will be
+autoloaded.
+
+```php
+$log = new Monolog\Logger('name');
+$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Logger::WARNING));
+
+$log->addWarning('Foo');
+```
+
+You can even add your own code to the autoloader by adding an `autoload` key
+to `composer.json`.
+
+```json
+{
+    "autoload": {
+        "psr-0": {"Acme": "src/"}
+    }
+}
+```
+
+This is a mapping from namespaces to directories. The `src` directory would be
+in your project root. An example filename would be `src/Acme/Foo.php`
+containing a `Acme\Foo` class.
+
+After adding the `autoload` key, you have to re-run `install` to re-generate
+the `vendor/.composer/autoload.php` file.
+
+Including that file will also return the autoloader instance, so you can add
+retrieve it and add more namespaces. This can be useful for autoloading
+classes in a test suite, for example.
+
+```php
+$loader = require 'vendor/.composer/autoload.php';
+$loader->add('Acme\Test', __DIR__);
+```
+
+> **Note:** Composer provides its own autoloader. If you don't want to use
+that one, you can just include `vendor/.composer/autoload_namespaces.php`,
+which returns an associative array mapping namespaces to directories.

+ 168 - 0
doc/02-libraries.md

@@ -0,0 +1,168 @@
+# Libraries
+
+This chapter will tell you how to make your library installable through composer.
+
+## Every project is a package
+
+As soon as you have a `composer.json` in a directory, that directory is a
+package. When you add a `require` to a project, you are making a package that
+depends on other packages. The only difference between your project and
+libraries is that your project is a package without a name.
+
+In order to make that package installable you need to give it a name. You do
+this by adding a `name` to `composer.json`:
+
+```json
+{
+    "name": "acme/hello-world",
+    "require": {
+        "monolog/monolog": "1.0.*"
+    }
+}
+```
+
+In this case the project name is `acme/hello-world`, where `acme` is the
+vendor name. Supplying a vendor name is mandatory.
+
+> **Note:** If you don't know what to use as a vendor name, your GitHub
+username is usually a good bet. While package names are case insensitive, the
+convention is all lowercase and dashes for word separation.
+
+## Specifying the version
+
+You need to specify the version some way. Depending on the type of repository
+you are using, it might be possible to omit it from `composer.json`, because
+the repository is able to infer the version from elsewhere.
+
+If you do want to specify it explicitly, you can just add a `version` field:
+
+```json
+{
+    "version": "1.0.0"
+}
+```
+
+However if you are using git, svn or hg, you don't have to specify it.
+Composer will detect versions as follows:
+
+### Tags
+
+For every tag that looks like a version, a package version of that tag will be
+created. It should match 'X.Y.Z' or 'vX.Y.Z', with an optional suffix for RC,
+beta, alpha or patch.
+
+Here are a few examples of valid tag names:
+
+    1.0.0
+    v1.0.0
+    1.10.5-RC1
+    v4.4.4beta2
+    v2.0.0-alpha
+    v2.0.4-p1
+
+> **Note:** If you specify an explicit version in `composer.json`, the tag name must match the specified version.
+
+### Branches
+
+For every branch, a package development version will be created. If the branch
+name looks like a version, the version will be `{branchname}-dev`. For example
+a branch `2.0` will get a version `2.0-dev`. If the branch does not look like
+a version, it will be `dev-{branchname}`. `master` results in a `dev-master`
+version.
+
+Here are some examples of version branch names:
+
+    1.0
+    1.*
+    1.1.x
+    1.1.*
+
+> **Note:** When you install a dev version, it will install it from source.
+See [Repositories] for more information.
+
+## Lock file
+
+For projects it is recommended to commit the `composer.lock` file into version
+control. For libraries this is not the case. You do not want your library to
+be tied to exact versions of the dependencies. It should work with any
+compatible version, so make sure you specify your version constraints so that
+they include all compatible versions.
+
+**Do not commit your library's `composer.lock` into version control.**
+
+If you are using git, add it to the `.gitignore`.
+
+## Publishing to a VCS
+
+Once you have a vcs repository (version control system, e.g. git) containing a
+`composer.json` file, your library is already composer-installable. In this
+example we will publish the `acme/hello-world` library on GitHub under
+`github.com/composer/hello-world`.
+
+Now, To test installing the `acme/hello-world` package, we create a new
+project locally. We will call it `acme/blog`. This blog will depend on `acme
+/hello-world`, which in turn depends on `monolog/monolog`. We can accomplish
+this by creating a new `blog` directory somewhere, containing a
+`composer.json`:
+
+```json
+{
+    "name": "acme/blog",
+    "require": {
+        "acme/hello-world": "dev-master"
+    }
+}
+```
+
+The name is not needed in this case, since we don't want to publish the blog
+as a library. It is added here to clarify which `composer.json` is being
+described.
+
+Now we need to tell the blog app where to find the `hello-world` dependency.
+We do this by adding a package repository specification to the blog's
+`composer.json`:
+
+```json
+{
+    "name": "acme/blog",
+    "repositories": {
+        "acme/hello-world": {
+            "vcs": { "url": "https://github.com/composer/hello-world" }
+        }
+    },
+    "require": {
+        "acme/hello-world": "dev-master"
+    }
+}
+```
+
+For more details on how package repositories work and what other types are
+available, see [Repositories].
+
+That's all. You can now install the dependencies by running composer's
+`install` command!
+
+**Recap:** Any git/svn/hg repository containing a `composer.json` can be added
+to your project by specifying the package repository and declaring the
+dependency in the `require` field.
+
+## Publishing to packagist
+
+Alright, so now you can publish packages. But specifying the vcs repository
+every time is cumbersome. You don't want to force all your users to do that.
+
+The other thing that you may have noticed is that we did not specify a package
+repository for `monolog/monolog`. How did that work? The answer is packagist.
+
+[Packagist](http://packagist.org/) is the main package repository for
+composer, and it is enabled by default. Anything that is published on
+packagist is available automatically through composer. Since monolog
+[is on packagist](http://packagist.org/packages/monolog/monolog), we can depend
+on it without having to specify any additional repositories.
+
+Assuming we want to share `hello-world` with the world, we would want to
+publish it on packagist as well. And this is really easy.
+
+You simply hit the big "Submit Package" button and sign up. Then you submit
+the URL to your VCS repository, at which point packagist will start crawling
+it. Once it is done, your package will be available to anyone.

+ 139 - 0
doc/03-cli.md

@@ -0,0 +1,139 @@
+# Command-line interface
+
+You've already learned how to use the command-line interface to do some
+things. This chapter documents all the available commands.
+
+## init
+
+In the [Libraries] chapter we looked at how to create a `composer.json` by
+hand. There is also an `init` command available that makes it a bit easier to
+do this.
+
+When you run the command it will interactively ask you to fill in the fields,
+while using some smart defaults.
+
+    $ php composer.phar init
+
+## install
+
+The `install` command reads the `composer.json` file from the current
+directory, resolves the dependencies, and installs them into `vendor`.
+
+    $ php composer.phar install
+
+If there is a `composer.lock` file in the current directory, it will use the
+exact versions from there instead of resolving them. This ensures that
+everyone using the library will get the same versions of the dependencies.
+
+If there is no `composer.lock` file, composer will create one after dependency
+resolution.
+
+### Options
+
+* **--prefer-source:** There are two ways of downloading a package: `source` and `dist`. For stable versions composer will use the `dist` by default. The `source` is a version control repository. If `--prefer-source` is enabled, composer will install from `source` if there is one. This is useful if you want to make a bugfix to a project and get a local git clone of the dependency directly.
+* **--dry-run:** If you want to run through an installation without actually installing a package, you can use `--dry-run`. This will simulate the installation and show you what would happen.
+* **--no-install-recommends:** By default composer will install all packages that are referenced by `recommend`. By passing this option you can disable that.
+* **--install-suggests:** The packages referenced by `suggest` will not be installed by default. By passing this option, you can install them.
+
+## update
+
+In order to get the latest versions of the dependencies and to update the
+`composer.lock` file, you should use the `update` command.
+
+    $ php composer.phar update
+
+This will resolve all dependencies of the project and write the exact versions
+into `composer.lock`.
+
+### Options
+
+* **--prefer-source:** Install packages from `source` when available.
+* **--dry-run:** Simulate the command without actually doing anything.
+* **--no-install-recommends:** Do not install packages referenced by `recommend`.
+* **--install-suggests:** Install packages referenced by `suggest`.
+
+## search
+
+The search command allows you to search through the current project's package
+repositories. Usually this will be just packagist. You simply pass it the
+terms you want to search for.
+
+    $ php composer.phar search monolog
+
+You can also search for more than one term by passing multiple arguments.
+
+## show
+
+To list all of the available packages, you can use the `show` command.
+
+    $ php composer.phar show
+
+If you want to see the details of a certain package, you can pass the package
+name.
+
+    $ php composer.phar show monolog/monolog
+
+    name     : monolog/monolog
+    versions : master-dev, 1.0.2, 1.0.1, 1.0.0, 1.0.0-RC1
+    type     : library
+    names    : monolog/monolog
+    source   : [git] http://github.com/Seldaek/monolog.git 3d4e60d0cbc4b888fe5ad223d77964428b1978da
+    dist     : [zip] http://github.com/Seldaek/monolog/zipball/3d4e60d0cbc4b888fe5ad223d77964428b1978da 3d4e60d0cbc4b888fe5ad223d77964428b1978da
+    license  : MIT
+
+    autoload
+    psr-0
+    Monolog : src/
+
+    requires
+    php >=5.3.0
+
+You can even pass the package version, which will tell you the details of that
+specific version.
+
+    $ php composer.phar show monolog/monolog 1.0.2
+
+### Options
+
+* **--installed:** Will list the packages that are installed.
+* **--platform:** Will list only [Platform packages].
+
+## depends
+
+The `depends` command tells you which other packages depend on a certain
+package. You can specify which link types (`require`, `recommend`, `suggest`)
+should be included in the listing.
+
+    $ php composer.phar depends --link-type=require monolog/monolog
+
+    nrk/monolog-fluent
+    poc/poc
+    propel/propel
+    symfony/monolog-bridge
+    symfony/symfony
+
+### Options
+
+* **--link-type:** The link types to match on, can be specified multiple
+times.
+
+## validate
+
+You should always run the `validate` command before you commit your
+`composer.json` file, and before you tag a release. It will check if your
+`composer.json` is valid.
+
+    $ php composer.phar validate
+
+## self-update
+
+To update composer itself to the latest version, just run the `self-update`
+command. It will replace your `composer.phar` with the latest version.
+
+    $ php composer.phar self-update
+
+## help
+
+To get more information about a certain command, just use `help`.
+
+    $ php composer.phar help install

+ 417 - 0
doc/04-schema.md

@@ -0,0 +1,417 @@
+# composer.json
+
+This chapter will explain all of the options available in `composer.json`.
+
+## JSON schema
+
+We have a [JSON schema](http://json-schema.org) that documents the format and
+can also be used to validate your `composer.json`. In fact, it is used by the
+`validate` command. You can find it at: [`Resources/composer-
+schema.json`](https://github.com/composer/composer/blob/master/res
+/composer-schema.json).
+
+## Package root
+
+The root of the package definition is a JSON object.
+
+## name
+
+The name of the package. It consists of vendor name and project name,
+separated by `/`.
+
+Examples:
+
+* monolog/monolog
+* igorw/event-source
+
+Required for published packages (libraries).
+
+## description
+
+A short description of the package. Usually this is just one line long.
+
+Optional but recommended.
+
+## version
+
+The version of the package.
+
+This must follow the format of `X.Y.Z` with an optional suffix of `-dev`,
+`alphaN`, `-betaN` or `-RCN`.
+
+Examples:
+
+    1.0.0
+    1.0.2
+    1.1.0
+    0.2.5
+    1.0.0-dev
+    1.0.0-beta2
+    1.0.0-RC5
+
+Optional if the package repository can infer the version from somewhere, such
+as the VCS tag name in the VCS repository. In that case it is also recommended
+to omit it.
+
+## type
+
+The type of the package. It defaults to `library`.
+
+Package types are used for custom installation logic. If you have a package
+that needs some special logic, you can define a custom type. This could be a
+`symfony-bundle`, a `wordpress-plugin` or a `typo3-module`. These will all be
+specific to certain projects, and they will need to provide an installer
+capable of installing packages of that type.
+
+Out of the box, composer supports two types:
+
+* **library:** This is the default. It will simply copy the files to `vendor`.
+* **composer-installer:** A package of type `composer-installer` provides an
+installer for other packages that have a custom type. Symfony could supply a
+`symfony/bundle-installer` package, which every bundle would depend on.
+Whenever you install a bundle, it will fetch the installer and register it, in
+order to be able to install the bundle.
+
+Only use a custom type if you need custom logic during installation. It is
+recommended to omit this field and have it just default to `library`.
+
+## keywords
+
+An array of keywords that the package is related to. These can be used for
+searching and filtering.
+
+Examples:
+
+    logging
+    events
+    database
+    redis
+    templating
+
+Optional.
+
+## homepage
+
+An URL to the website of the project.
+
+Optional.
+
+## time
+
+Release date of the version.
+
+Must be in `YYYY-MM-DD` or `YYYY-MM-DD HH:MM:SS` format.
+
+Optional.
+
+## license
+
+The license of the package. This can be either a string or an array of strings.
+
+The recommended notation for the most common licenses is:
+
+    MIT
+    BSD-2
+    BSD-3
+    BSD-4
+    GPLv2
+    GPLv3
+    LGPLv2
+    LGPLv3
+    Apache2
+    WTFPL
+
+Optional, but it is highly recommended to supply this.
+
+## authors
+
+The authors of the package. This is an array of objects.
+
+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.
+
+An example:
+
+```json
+{
+    "authors": [
+        {
+            "name": "Nils Adermann",
+            "email": "naderman@naderman.de",
+            "homepage": "http://www.naderman.de"
+        },
+        {
+            "name": "Jordi Boggiano",
+            "email": "j.boggiano@seld.be",
+            "homepage": "http://seld.be"
+        }
+    ]
+}
+```
+
+Optional, but highly recommended.
+
+## Link types
+
+Each of these takes an object which maps package names to version constraints.
+
+* **require:** Packages required by this package.
+* **recommend:** Recommended packages, installed by default.
+* **suggest:**  Suggested packages. These are displayed after installation,
+  but not installed by default.
+* **conflict:** Mark this version of this package as conflicting with other
+  packages.
+* **replace:** Packages that can be replaced by this package. This is useful
+  for large repositories with subtree splits. It allows the main package to
+  replace all of it's child packages.
+* **provide:** List of other packages that are provided by this package. This
+  is mostly useful for common interfaces. A package could depend on some virtual
+  `logger` package, any library that provides this logger, would simply list it
+  in `provide`.
+
+Example:
+
+```json
+{
+    "require": {
+        "monolog/monolog": "1.0.*"
+    }
+}
+```
+
+Optional.
+
+## autoload
+
+Autoload mapping for a PHP autoloader.
+
+Currently only [PSR-0](https://github.com/php-fig/fig-
+standards/blob/master/accepted/PSR-0.md) autoloading is supported. Under the
+`psr-0` key you define a mapping from namespaces to paths, relative to the
+package root.
+
+Example:
+
+```json
+{
+    "autoload": {
+        "psr-0": { "Monolog": "src/" }
+    }
+}
+```
+
+Optional, but it is highly recommended that you follow PSR-0 and use this.
+
+## target-dir
+
+Defines the installation target.
+
+In case the package root is below the namespace declaration you cannot
+autoload properly. `target-dir` solves this problem.
+
+An example is Symfony. There are individual packages for the components. The
+Yaml component is under `Symfony\Component\Yaml`. The package root is that
+`Yaml` directory. To make autoloading possible, we need to make sure that it
+is not installed into `vendor/symfony/yaml`, but instead into
+`vendor/symfony/yaml/Symfony/Component/Yaml`, so that the autoloader can load
+it from `vendor/symfony/yaml`.
+
+To do that, `autoload` and `target-dir` are defined as follows:
+
+```json
+{
+    "autoload": {
+        "psr-0": { "Symfony\\Component\\Yaml": "" }
+    },
+    "target-dir": "Symfony/Component/Yaml"
+}
+```
+
+Optional.
+
+## repositories
+
+Custom package repositories to use.
+
+By default composer just uses the packagist repository. By specifying
+repositories you can get packages from elsewhere.
+
+Repositories are not resolved recursively. You can only add them to your main
+`composer.json`. Repository declarations of dependencies' `composer.json`s are
+ignored.
+
+Following repository types are supported:
+
+* **composer:** A composer repository is simply a `packages.json` file served
+  via HTTP that contains a list of `composer.json` objects with additional
+  `dist` and/or `source` information.
+* **vcs:** The version control system repository can fetch packages from git,
+  svn and hg repositories. Note the distinction between package repository and
+  version control repository.
+* **pear:** With this you can import any pear repository into your composer
+  project.
+* **package:** If you depend on a project that does not have any support for
+  composer whatsoever you can define the package inline using a `package`
+  repository. You basically just inline the `composer.json` object.
+
+For more information on any of these, see [Repositories].
+
+Example:
+
+```json
+{
+    "repositories": [
+        {
+            "type": "composer",
+            "url": "http://packages.example.com"
+        },
+        {
+            "type": "vcs",
+            "url": "https://github.com/Seldaek/monolog"
+        },
+        {
+            "type": "pear",
+            "url": "http://pear2.php.net"
+        },
+        {
+            "type": "package",
+            "package": {
+                "name": "smarty/smarty",
+                "version": "3.1.7",
+                "dist": {
+                    "url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
+                    "type": "zip"
+                },
+                "source": {
+                    "url": "http://smarty-php.googlecode.com/svn/",
+                    "type": "svn",
+                    "reference": "tags/Smarty_3_1_7/distribution/"
+                }
+            }
+        }
+    ]
+}
+```
+
+> **Note:** Order is significant here. Repositories added later will take
+precedence. This also means that custom repositories can override packages
+that exist on packagist.
+
+You can also disable the packagist repository by setting `packagist` to
+`false`.
+
+```json
+{
+    "repositories": [
+        {
+            "packagist": false
+        }
+    ]
+}
+```
+
+## config
+
+A set of configuration options. It is only used for projects.
+
+The following options are supported:
+
+* **vendor-dir:** Defaults to `vendor`. You can install dependencies into a
+  different directory if you want to.
+* **bin-dir:** Defaults to `vendor/bin`. If a project includes binaries, they
+  will be symlinked into this directory.
+
+Example:
+
+```json
+{
+    "config": {
+        "bin-dir": "bin"
+    }
+}
+```
+
+## scripts
+
+Composer allows you to hook into various parts of the installation process through the use of scripts.
+
+These events are supported:
+
+* **pre-install-cmd:** Occurs before the install command is executed, contains
+  one or more Class::method callables.
+* **post-install-cmd:** Occurs after the install command is executed, contains
+  one or more Class::method callables.
+* **pre-update-cmd:** Occurs before the update command is executed, contains
+  one or more Class::method callables.
+* **post-update-cmd:** Occurs after the update command is executed, contains
+  one or more Class::method callables.
+* **pre-package-install:** Occurs before a package is installed, contains one
+  or more Class::method callables.
+* **post-package-install:** Occurs after a package is installed, contains one
+  or more Class::method callables.
+* **pre-package-update:** Occurs before a package is updated, contains one or
+  more Class::method callables.
+* **post-package-update:** Occurs after a package is updated, contains one or
+  more Class::method callables.
+* **pre-package-uninstall:** Occurs before a package has been uninstalled,
+  contains one or more Class::method callables.
+* **post-package-uninstall:** Occurs after a package has been uninstalled,
+  contains one or more Class::method callables.
+
+For each of these events you can provide a static method on a class that will
+handle it.
+
+Example:
+
+```json
+{
+    "scripts": {
+        "post-install-cmd": [
+            "Acme\\ScriptHandler::doSomething"
+        ]
+    }
+}
+```
+
+The event handler receives a `Composer\Script\Event` object as an argument,
+which gives you access to the `Composer\Composer` instance through the
+`getComposer` method.
+
+```php
+namespace Acme;
+
+use Composer\Script\Event;
+
+class ScriptHandler
+{
+    static public function doSomething(Event $event)
+    {
+        // custom logic
+    }
+}
+```
+
+## extra
+
+Arbitrary extra data for consumption by `scripts`.
+
+This can be virtually anything. To access it from within a script event
+handler, you can do:
+
+```php
+$extra = $event->getComposer()->getPackage()->getExtra();
+```
+
+Optional.
+
+## bin
+
+A set of files that should be treated as binaries and symlinked into the `bin-
+dir` (from config).
+
+See [articles/bin.md] for more details.
+
+Optional.

+ 263 - 0
doc/05-repositories.md

@@ -0,0 +1,263 @@
+# Repositories
+
+This chapter will explain the concept of packages and repositories, what kinds
+of repositories are available, and how they work.
+
+## Concepts
+
+Before we look at the different types of repositories that we can have, we
+need to understand some of the basic concepts that composer is built on.
+
+### Package
+
+Composer is a dependency manager. It installs packages. A package is
+essentially just a directory containing something. In this case it is PHP
+code, but in theory it could be anything. And it contains a package
+description which has a name and a version. The name and the version are used
+to identify the package.
+
+In fact, internally composer sees every version as a separate package. While
+this distinction does not matter when you are using composer, it's quite
+important when you want to change it.
+
+In addition to the name and the version, there is useful data. The only really
+important piece of information is the package source, that describes where to
+get the package contents. The package data points to the contents of the
+package. And there are two options here: dist and source.
+
+**Dist:** The dist is a packaged version of the package data. Usually a
+released version, usually a stable release.
+
+**Source:** The source is used for development. This will usually originate
+from a source code repository, such as git. You can fetch this when you want
+to modify the downloaded package.
+
+Packages can supply either of these, or even both. Depending on certain
+factors, such as user-supplied options and stability of the package, one will
+be preferred.
+
+### Repository
+
+A repository is a package source. It's a list of packages, of which you can
+pick some to install.
+
+You can also add more repositories to your project by declaring them in
+`composer.json`.
+
+## Types
+
+### 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:
+
+```json
+{
+    "vendor/packageName": {
+        "name": "vendor/packageName",
+        "description": "Package description",
+        "versions": {
+            "master-dev": { @composer.json },
+            "1.0.0": { @composer.json }
+        }
+    }
+}
+```
+
+The `@composer.json` marker would be the contents of the `composer.json` from
+that package version including as a minimum:
+
+* name
+* version
+* dist or source
+
+Here is a minimal package definition:
+
+```json
+{
+    "name": "smarty/smarty",
+    "version": "3.1.7",
+    "dist": {
+        "url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
+        "type": "zip"
+    }
+}
+```
+
+It may include any of the other fields specified in the [schema].
+
+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.org` the repository URL would be
+`http://example.org`.
+
+### VCS
+
+VCS stands for version control system. This includes versioning systems like
+git, svn or hg. Composer has a repository type for installing packages from
+these systems.
+
+There are a few use cases for this. The most common one is maintaining your
+own fork of a third party library. If you are using a certain library for your
+project and you decide to change something in the library, you will want your
+project to use the patched version. If the library is on GitHub (this is the
+case most of the time), you can simply fork it there and push your changes to
+your fork. After that you update the project's `composer.json`. All you have
+to do is add your fork as a repository and update the version constraint to
+point to your custom branch.
+
+Example assuming you patched monolog to fix a bug in the `bugfix` branch:
+
+```json
+{
+    "repositories": [
+        {
+            "type": "vcs",
+            "url": "http://github.com/igorw/monolog"
+        }
+    ],
+    "require": {
+        "monolog/monolog": "dev-bugfix"
+    }
+}
+```
+
+When you run `php composer.phar update`, you should get your modified version
+of `monolog/monolog` instead of the one from packagist.
+
+Git is not the only version control system supported by the VCS repository.
+The following are supported:
+
+* **Git:** [git-scm.com](http://git-scm.com)
+* **Subversion:** [subversion.apache.org](http://subversion.apache.org)
+* **Mercurial:** [mercurial.selenic.com](http://mercurial.selenic.com)
+
+To use these systems you need to have them installed. That can be
+invonvenient. And for this reason there is special support for GitHub and
+BitBucket that use the APIs provided by these sites, to fetch the packages
+without having to install the version control system. The VCS repository
+provides `dist`s for them that fetch the packages as zips.
+
+* **GitHub:** [github.com](https://github.com) (Git)
+* **BitBucket:** [bitbucket.org](https://bitbucket.org) (Git and Mercurial)
+
+The VCS driver to be used is detected automatically based on the URL.
+
+### PEAR
+
+It is possible to install packages from any PEAR channel by using the `pear`
+repository. Composer will prefix all package names with `pear-{channelName}/` to
+avoid conflicts.
+
+Example using `pear2.php.net`:
+
+```json
+{
+    "repositories": [
+        {
+            "type": "pear",
+            "url": "http://pear2.php.net"
+        }
+    ],
+    "require": {
+        "pear-pear2/PEAR2_HTTP_Request": "*"
+    }
+}
+```
+
+In this case the short name of the channel is `pear2`, so the
+`PEAR2_HTTP_Request` package name becomes `pear-pear2/PEAR2_HTTP_Request`.
+
+> **Note:** The `pear` repository requires doing quite a few requests per
+> package, so this may considerably slow down the installation process.
+
+### Package
+
+If you want to use a project that does not support composer through any of the
+means above, you still can define the package yourself using a `package`
+repository.
+
+Basically, you define the same information that is included in the `composer`
+repository's `packages.json`, but only for a single package. Again, the
+minimally required fields are `name`, `version`, and either of `dist` or
+`source`.
+
+Here is an example for the smarty template engine:
+
+```json
+{
+    "repositories": [
+        {
+            "type": "package",
+            "package": {
+                "name": "smarty/smarty",
+                "version": "3.1.7",
+                "dist": {
+                    "url": "http://www.smarty.net/files/Smarty-3.1.7.zip",
+                    "type": "zip"
+                },
+                "source": {
+                    "url": "http://smarty-php.googlecode.com/svn/",
+                    "type": "svn",
+                    "reference": "tags/Smarty_3_1_7/distribution/"
+                }
+            }
+        }
+    ],
+    "require": {
+        "smarty/smarty": "3.1.*"
+    }
+}
+```
+
+Typically you would leave the source part off, as you don't really need it.
+
+## Hosting your own
+
+While you will probably want to put your packages on packagist most of the time,
+there are some use cases for hosting your own repository.
+
+* **Private company packages:** If you are part of a company that uses composer
+  for their packages internally, you might want to keep those packages private.
+
+* **Separate ecosystem:** If you have a project which has its own ecosystem,
+  and the packages aren't really reusable by the greater PHP community, you
+  might want to keep them separate to packagist. An example of this would be
+  wordpress plugins.
+
+When hosting your own package repository it is recommended to use a `composer`
+one. This is type that is native to composer and yields the best performance.
+
+There are a few different tools that can help you create a `composer`
+repository.
+
+### Packagist
+
+The underlying application used by packagist is open source. This means that you
+can just install your own copy of packagist, re-brand, and use it. It's really
+quite straight-forward to do.
+
+Packagist is a Symfony2 application, and it is [available on
+GitHub](https://github.com/composer/packagist). It uses composer internally and
+acts as a proxy between VCS repositories and the composer users. It holds a list
+of all VCS packages, periodically re-crawls them, and exposes them as a composer
+repository.
+
+To set your own copy, simply follow the instructions from the [packagist
+github repository](https://github.com/composer/packagist).
+
+### Satis
+
+Satis is a static `composer` repository generator. It is a bit like a ultra-
+lightweight, file-based version of packagist.
+
+You give it a `composer.json` containing repositories, typically VCS and package
+repository definitions. It will fetch all the packages that are `require`d from
+these repositories and dump a `packages.json` that is your `composer`
+repository.
+
+Check [the satis GitHub repository](https://github.com/composer/satis) for more
+information.

+ 28 - 0
doc/06-community.md

@@ -0,0 +1,28 @@
+# Community
+
+We have a lot of people using composer, and also many contributors to the
+project.
+
+## Contributing
+
+If you would like to contribute to composer, please read the
+[README](https://github.com/composer/composer).
+
+The most important guidelines are described as follows:
+
+> All code contributions - including those of people having commit access - must
+> go through a pull request and approved by a core developer before being
+> merged. This is to ensure proper review of all the code.
+>
+> Fork the project, create a feature branch, and send us a pull request.
+>
+> To ensure a consistent code base, you should make sure the code follows
+> the [Coding Standards](http://symfony.com/doc/2.0/contributing/code/standards.html)
+> which we borrowed from Symfony.
+
+## IRC / mailing list
+
+The developer mailing list is on [google groups](http://groups.google.com/group
+/composer-dev) IRC channels are available for discussion as well, on
+irc.freenode.org [#composer](irc://irc.freenode.org/composer) for users and
+[#composer-dev](irc://irc.freenode.org/composer-dev) for development.

+ 0 - 0
doc/faqs/packagist-update-schedule.md → doc/articles/packagist-update-schedule.md


+ 0 - 0
doc/faqs/scripts.md → doc/articles/scripts.md


+ 0 - 0
doc/faqs/vendor-bins.md → doc/articles/vendor-bins.md


+ 0 - 0
doc/DefaultPolicy.md → doc/dev/DefaultPolicy.md


+ 67 - 0
doc/composer-schema.json → res/composer-schema.json

@@ -101,6 +101,20 @@
             "description": "This is a hash of package name (keys) and version constraints (values) that this package suggests work well with it (typically this will only be suggested to the user).",
             "additionalProperties": true
         },
+        "config": {
+            "type": ["object"],
+            "description": "Composer options.",
+            "properties": {
+                "vendor-dir": {
+                    "type": "string",
+                    "description": "The location where all packages are installed, defaults to \"vendor\"."
+                },
+                "bin-dir": {
+                    "type": "string",
+                    "description": "The location where all binaries are linked, defaults to \"vendor/bin\"."
+                }
+            }
+        },
         "extra": {
             "type": ["object", "array"],
             "description": "Arbitrary extra data that can be used by custom installers, for example, package of type composer-installer must have a 'class' key defining the installer class name.",
@@ -121,6 +135,59 @@
             "type": ["object", "array"],
             "description": "A set of additional repositories where packages can be found.",
             "additionalProperties": true
+        },
+        "bin": {
+            "type": ["array"],
+            "description": "A set of files that should be treated as binaries and symlinked into bin-dir (from config).",
+            "items": {
+                "type": "string"
+            }
+        },
+        "scripts": {
+            "type": ["object"],
+            "description": "Scripts listeners that will be executed before/after some events.",
+            "properties": {
+                "pre-install-cmd": {
+                    "type": ["array", "string"],
+                    "description": "Occurs before the install command is executed, contains one or more Class::method callables."
+                },
+                "post-install-cmd": {
+                    "type": ["array", "string"],
+                    "description": "Occurs after the install command is executed, contains one or more Class::method callables."
+                },
+                "pre-update-cmd": {
+                    "type": ["array", "string"],
+                    "description": "Occurs before the update command is executed, contains one or more Class::method callables."
+                },
+                "post-update-cmd": {
+                    "type": ["array", "string"],
+                    "description": "Occurs after the update command is executed, contains one or more Class::method callables."
+                },
+                "pre-package-install": {
+                    "type": ["array", "string"],
+                    "description": "Occurs before a package is installed, contains one or more Class::method callables."
+                },
+                "post-package-install": {
+                    "type": ["array", "string"],
+                    "description": "Occurs after a package is installed, contains one or more Class::method callables."
+                },
+                "pre-package-update": {
+                    "type": ["array", "string"],
+                    "description": "Occurs before a package is updated, contains one or more Class::method callables."
+                },
+                "post-package-update": {
+                    "type": ["array", "string"],
+                    "description": "Occurs after a package is updated, contains one or more Class::method callables."
+                },
+                "pre-package-uninstall": {
+                    "type": ["array", "string"],
+                    "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables."
+                },
+                "post-package-uninstall": {
+                    "type": ["array", "string"],
+                    "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables."
+                }
+            }
         }
     }
 }

+ 9 - 2
src/Composer/Command/DependsCommand.php

@@ -25,6 +25,8 @@ use Symfony\Component\Console\Output\OutputInterface;
  */
 class DependsCommand extends Command
 {
+    protected $linkTypes = array('require', 'recommend', 'suggest');
+
     protected function configure()
     {
         $this
@@ -32,7 +34,7 @@ class DependsCommand extends Command
             ->setDescription('Shows which packages depend on the given package')
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
-                new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', array('requires', 'recommends', 'suggests'))
+                new InputOption('link-type', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Link types to show', $this->linkTypes)
             ))
             ->setHelp(<<<EOT
 Displays detailed information about where a package is referenced.
@@ -74,10 +76,15 @@ EOT
 
         $repos = $composer->getRepositoryManager()->getRepositories();
         $types = $input->getOption('link-type');
+
         foreach ($repos as $repository) {
             foreach ($repository->getPackages() as $package) {
                 foreach ($types as $type) {
-                    foreach ($package->{'get'.$type}() as $link) {
+                    $type = rtrim($type, 's');
+                    if (!in_array($type, $this->linkTypes)) {
+                        throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', $this->linkTypes));
+                    }
+                    foreach ($package->{'get'.$type.'s'}() as $link) {
                         if ($link->getTarget() === $needle) {
                             if ($verbose) {
                                 $references[] = array($type, $package, $link);

+ 45 - 29
src/Composer/Command/InstallCommand.php

@@ -30,6 +30,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Composer\DependencyResolver\Operation\InstallOperation;
+use Composer\DependencyResolver\Operation\UpdateOperation;
 use Composer\DependencyResolver\Solver;
 use Composer\IO\IOInterface;
 
@@ -113,23 +114,20 @@ EOT
         }
 
         // creating requirements request
+        $installFromLock = false;
         $request = new Request($pool);
         if ($update) {
             $io->write('<info>Updating dependencies</info>');
             $installedPackages = $installedRepo->getPackages();
             $links = $this->collectLinks($composer->getPackage(), $noInstallRecommends, $installSuggests);
 
-            foreach ($links as $link) {
-                foreach ($installedPackages as $package) {
-                    if ($package->getName() === $link->getTarget()) {
-                        $request->update($package->getName(), new VersionConstraint('=', $package->getVersion()));
-                        break;
-                    }
-                }
+            $request->updateAll();
 
+            foreach ($links as $link) {
                 $request->install($link->getTarget(), $link->getConstraint());
             }
         } elseif ($composer->getLocker()->isLocked()) {
+            $installFromLock = true;
             $io->write('<info>Installing from lock file</info>');
 
             if (!$composer->getLocker()->isFresh()) {
@@ -158,45 +156,63 @@ EOT
         // solve dependencies
         $operations = $solver->solve($request);
 
-        // check for missing deps
-        // TODO this belongs in the solver, but this will do for now to report top-level deps missing at least
-        foreach ($request->getJobs() as $job) {
-            if ('install' === $job['cmd']) {
-                foreach ($installedRepo->getPackages() as $package ) {
-                    if ($installedRepo->hasPackage($package) && !$package->isPlatform() && !$installationManager->isPackageInstalled($package)) {
-                        $operations[$job['packageName']] = new InstallOperation($package, Solver::RULE_PACKAGE_NOT_EXIST);
-                    }
-                    if (in_array($job['packageName'], $package->getNames())) {
-                        continue 2;
-                    }
+        // execute operations
+        if (!$operations) {
+            $io->write('<info>Nothing to install/update</info>');
+        }
+
+        // force dev packages to be updated to latest reference on update
+        if ($update) {
+            foreach ($localRepo->getPackages() as $package) {
+                // skip non-dev packages
+                if (!$package->isDev()) {
+                    continue;
                 }
+
+                // skip packages that will be updated/uninstalled
                 foreach ($operations as $operation) {
-                    if ('install' === $operation->getJobType() && in_array($job['packageName'], $operation->getPackage()->getNames())) {
-                        continue 2;
-                    }
-                    if ('update' === $operation->getJobType() && in_array($job['packageName'], $operation->getTargetPackage()->getNames())) {
+                    if (('update' === $operation->getJobType() && $package === $operation->getInitialPackage())
+                        || ('uninstall' === $operation->getJobType() && $package === $operation->getPackage())
+                    ) {
                         continue 2;
                     }
                 }
 
-                if ($pool->whatProvides($job['packageName'])) {
-                    throw new \UnexpectedValueException('Package '.$job['packageName'].' can not be installed, either because its version constraint is incorrect, or because one of its dependencies was not found.');
+                // force update
+                $newPackage = $composer->getRepositoryManager()->findPackage($package->getName(), $package->getVersion());
+                if ($newPackage && $newPackage->getSourceReference() !== $package->getSourceReference()) {
+                    $operations[] = new UpdateOperation($package, $newPackage);
                 }
-                throw new \UnexpectedValueException('Package '.$job['packageName'].' was not found in the package pool, check the name for typos.');
             }
         }
 
-        // execute operations
-        if (!$operations) {
-            $io->write('<info>Nothing to install/update</info>');
-        }
         foreach ($operations as $operation) {
             if ($verbose) {
                 $io->write((string) $operation);
             }
             if (!$dryRun) {
                 $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::PRE_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
+
+                // if installing from lock, restore dev packages' references to their locked state
+                if ($installFromLock) {
+                    $package = null;
+                    if ('update' === $operation->getJobType()) {
+                        $package = $operation->getTargetPackage();
+                    } elseif ('install' === $operation->getJobType()) {
+                        $package = $operation->getPackage();
+                    }
+                    if ($package && $package->isDev()) {
+                        $lockData = $composer->getLocker()->getLockData();
+                        foreach ($lockData['packages'] as $lockedPackage) {
+                            if (!empty($lockedPackage['source-reference']) && strtolower($lockedPackage['package']) === $package->getName()) {
+                                $package->setSourceReference($lockedPackage['source-reference']);
+                                break;
+                            }
+                        }
+                    }
+                }
                 $installationManager->execute($operation);
+
                 $eventDispatcher->dispatchPackageEvent(constant('Composer\Script\ScriptEvents::POST_PACKAGE_'.strtoupper($operation->getJobType())), $operation);
             }
         }

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

@@ -21,11 +21,6 @@ use Composer\Package\LinkConstraint\VersionConstraint;
  */
 class DefaultPolicy implements PolicyInterface
 {
-    public function allowUninstall()
-    {
-        return true;
-    }
-
     public function versionCompare(PackageInterface $a, PackageInterface $b, $operator)
     {
         $constraint = new VersionConstraint($operator, $b->getVersion());

+ 0 - 1
src/Composer/DependencyResolver/PolicyInterface.php

@@ -20,7 +20,6 @@ use Composer\Package\PackageInterface;
  */
 interface PolicyInterface
 {
-    function allowUninstall();
     function versionCompare(PackageInterface $a, PackageInterface $b, $operator);
     function findUpdatePackages(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);
     function installable(Solver $solver, Pool $pool, array $installedMap, PackageInterface $package);

+ 5 - 0
src/Composer/DependencyResolver/Request.php

@@ -55,6 +55,11 @@ class Request
         );
     }
 
+    public function updateAll()
+    {
+        $this->jobs[] = array('cmd' => 'update-all', 'packages' => array());
+    }
+
     public function getJobs()
     {
         return $this->jobs;

+ 0 - 2
src/Composer/DependencyResolver/RuleSet.php

@@ -20,7 +20,6 @@ class RuleSet implements \IteratorAggregate, \Countable
     // highest priority => lowest number
     const TYPE_PACKAGE = 0;
     const TYPE_JOB = 1;
-    const TYPE_UPDATE = 2;
     const TYPE_FEATURE = 3;
     const TYPE_CHOICE = 4;
     const TYPE_LEARNED = 5;
@@ -29,7 +28,6 @@ class RuleSet implements \IteratorAggregate, \Countable
         -1 => 'UNKNOWN',
         self::TYPE_PACKAGE => 'PACKAGE',
         self::TYPE_FEATURE => 'FEATURE',
-        self::TYPE_UPDATE => 'UPDATE',
         self::TYPE_JOB => 'JOB',
         self::TYPE_CHOICE => 'CHOICE',
         self::TYPE_LEARNED => 'LEARNED',

+ 89 - 55
src/Composer/DependencyResolver/Solver.php

@@ -52,7 +52,6 @@ class Solver
     protected $decisionMap;
     protected $installedMap;
 
-    protected $packageToUpdateRule = array();
     protected $packageToFeatureRule = array();
 
     public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed)
@@ -148,10 +147,6 @@ class Solver
      */
     protected function createInstallOneOfRule(array $packages, $reason, $reasonData = null)
     {
-        if (empty($packages)) {
-            return $this->createImpossibleRule($reason, $reasonData);
-        }
-
         $literals = array();
         foreach ($packages as $package) {
             $literals[] = new Literal($package, true);
@@ -201,22 +196,6 @@ class Solver
         return new Rule(array(new Literal($issuer, false), new Literal($provider, false)), $reason, $reasonData);
     }
 
-    /**
-     * Intentionally creates a rule impossible to solve
-     *
-     * The rule is an empty one so it can never be satisfied.
-     *
-     * @param int     $reason     A RULE_* constant describing the reason for
-     *                            generating this rule
-     * @param mixed   $reasonData Any data, e.g. the package name, that goes with
-     *                            the reason
-     * @return Rule               An empty rule
-     */
-    protected function createImpossibleRule($reason, $reasonData = null)
-    {
-        return new Rule(array(), $reason, $reasonData);
-    }
-
     /**
      * Adds a rule unless it duplicates an existing one of any type
      *
@@ -305,7 +284,7 @@ class Solver
             // if ignoreinstalledsobsoletes is not set, we're also checking
             // obsoletes of installed packages (like newer rpm versions)
             //
-            /** @TODO: if ($this->noInstalledObsoletes) */
+            /** TODO if ($this->noInstalledObsoletes) */
             if (true) {
                 $noObsoletes = isset($this->noObsoletes[$package->getId()]);
                 $isInstalled = (isset($this->installedMap[$package->getId()]));
@@ -508,7 +487,7 @@ class Solver
 
             // push all of our rules (can only be feature or job rules)
             // asserting this literal on the problem stack
-            foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_UPDATE, RuleSet::TYPE_FEATURE)) as $assertRule) {
+            foreach ($this->rules->getIteratorFor(array(RuleSet::TYPE_JOB, RuleSet::TYPE_FEATURE)) as $assertRule) {
                 if ($assertRule->isDisabled() || !$assertRule->isAssertion() || $assertRule->isWeak()) {
                     continue;
                 }
@@ -882,11 +861,6 @@ class Solver
 
     protected function disableUpdateRule($package)
     {
-        // find update & feature rule and disable
-        if (isset($this->packageToUpdateRule[$package->getId()])) {
-            $this->packageToUpdateRule[$package->getId()]->disable();
-        }
-
         if (isset($this->packageToFeatureRule[$package->getId()])) {
             $this->packageToFeatureRule[$package->getId()]->disable();
         }
@@ -958,6 +932,14 @@ class Solver
                         break;
                 }
             }
+
+            switch ($job['cmd']) {
+                case 'update-all':
+                    foreach ($installedPackages as $package) {
+                        $this->updateMap[$package->getId()] = true;
+                    }
+                break;
+            }
         }
 
         foreach ($installedPackages as $package) {
@@ -986,22 +968,21 @@ class Solver
             $updates = $this->policy->findUpdatePackages($this, $this->pool, $this->installedMap, $package);
             $rule = $this->createUpdateRule($package, $updates, self::RULE_INTERNAL_ALLOW_UPDATE, (string) $package);
 
-            if ($this->policy->allowUninstall()) {
-                $rule->setWeak(true);
-                $this->addRule(RuleSet::TYPE_FEATURE, $featureRule);
-                $this->packageToFeatureRule[$package->getId()] = $rule;
-            } else {
-                $this->addRule(RuleSet::TYPE_UPDATE, $rule);
-                $this->packageToUpdateRule[$package->getId()] = $rule;
-            }
+            $rule->setWeak(true);
+            $this->addRule(RuleSet::TYPE_FEATURE, $rule);
+            $this->packageToFeatureRule[$package->getId()] = $rule;
         }
 
         foreach ($this->jobs as $job) {
             switch ($job['cmd']) {
                 case 'install':
-                    $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']);
-                    $this->addRule(RuleSet::TYPE_JOB, $rule);
-                    $this->ruleToJob[$rule->getId()] = $job;
+                    if (empty($job['packages'])) {
+                        $this->problems[] = array($job);
+                    } else {
+                        $rule = $this->createInstallOneOfRule($job['packages'], self::RULE_JOB_INSTALL, $job['packageName']);
+                        $this->addRule(RuleSet::TYPE_JOB, $rule);
+                        $this->ruleToJob[$rule->getId()] = $job;
+                    }
                     break;
                 case 'remove':
                     // remove all packages with this name including uninstalled
@@ -1046,6 +1027,10 @@ class Solver
         //findrecommendedsuggested(solv);
         //solver_prepare_solutions(solv);
 
+        if ($this->problems) {
+            throw new SolverProblemsException($this->problems, $this->learnedPool);
+        }
+
         return $this->createTransaction();
     }
 
@@ -1061,9 +1046,6 @@ class Solver
             if (!$literal->isWanted() && isset($this->installedMap[$package->getId()])) {
                 $literals = array();
 
-                if (isset($this->packageToUpdateRule[$package->getId()])) {
-                    $literals = array_merge($literals, $this->packageToUpdateRule[$package->getId()]->getLiterals());
-                }
                 if (isset($this->packageToFeatureRule[$package->getId()])) {
                     $literals = array_merge($literals, $this->packageToFeatureRule[$package->getId()]->getLiterals());
                 }
@@ -1127,6 +1109,8 @@ class Solver
 
     protected function addDecision(Literal $l, $level)
     {
+        assert($this->decisionMap[$l->getPackageId()] == 0);
+
         if ($l->isWanted()) {
             $this->decisionMap[$l->getPackageId()] = $level;
         } else {
@@ -1137,6 +1121,9 @@ class Solver
     protected function addDecisionId($literalId, $level)
     {
         $packageId = abs($literalId);
+
+        assert($this->decisionMap[$packageId] == 0);
+
         if ($literalId > 0) {
             $this->decisionMap[$packageId] = $level;
         } else {
@@ -1179,8 +1166,8 @@ class Solver
     {
         $packageId = abs($literalId);
         return (
-            $this->decisionMap[$packageId] > 0 && !($literalId < 0) ||
-            $this->decisionMap[$packageId] < 0 && $literalId > 0
+            ($this->decisionMap[$packageId] > 0 && $literalId < 0) ||
+            ($this->decisionMap[$packageId] < 0 && $literalId > 0)
         );
     }
 
@@ -1227,7 +1214,8 @@ class Solver
                 continue;
             }
 
-            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $rule = $nextRule) {
+            $prevRule = null;
+            for ($rule = $this->watches[$literal->getId()]; $rule !== null; $prevRule = $rule, $rule = $nextRule) {
                 $nextRule = $rule->getNext($literal);
 
                 if ($rule->isDisabled()) {
@@ -1247,16 +1235,27 @@ class Solver
                         if ($otherWatch !== $ruleLiteral->getId() &&
                             !$this->decisionsConflict($ruleLiteral)) {
 
-
                             if ($literal->getId() === $rule->watch1) {
                                 $rule->watch1 = $ruleLiteral->getId();
-                                $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ;
+                                $rule->next1 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
                             } else {
                                 $rule->watch2 = $ruleLiteral->getId();
-                                $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null ;
+                                $rule->next2 = (isset($this->watches[$ruleLiteral->getId()])) ? $this->watches[$ruleLiteral->getId()] : null;
+                            }
+
+                            if ($prevRule) {
+                                if ($prevRule->next1 == $rule) {
+                                    $prevRule->next1 = $nextRule;
+                                } else {
+                                    $prevRule->next2 = $nextRule;
+                                }
+                            } else {
+                                $this->watches[$literal->getId()] = $nextRule;
                             }
 
                             $this->watches[$ruleLiteral->getId()] = $rule;
+
+                            $rule = $prevRule;
                             continue 2;
                         }
                     }
@@ -1487,7 +1486,7 @@ class Solver
         }
 
         $why = count($this->learnedPool) - 1;
-
+        assert($learnedLiterals[0] !== null);
         $newRule = new Rule($learnedLiterals, self::RULE_LEARNED, $why);
 
         return array($ruleLevel, $newRule, $why);
@@ -1813,11 +1812,7 @@ class Solver
 
                         $rule = null;
 
-                        if (isset($this->packageToUpdateRule[$literal->getPackageId()])) {
-                            $rule = $this->packageToUpdateRule[$literal->getPackageId()];
-                        }
-
-                        if ((!$rule || $rule->isDisabled()) && isset($this->packageToFeatureRule[$literal->getPackageId()])) {
+                        if (isset($this->packageToFeatureRule[$literal->getPackageId()])) {
                             $rule = $this->packageToFeatureRule[$literal->getPackageId()];
                         }
 
@@ -2027,8 +2022,10 @@ class Solver
             }
             if ($level > 0) {
                 echo '    +' . $this->pool->packageById($packageId)."\n";
-            } else {
+            } elseif ($level < 0) {
                 echo '    -' . $this->pool->packageById($packageId)."\n";
+            } else {
+                echo '    ?' . $this->pool->packageById($packageId)."\n";
             }
         }
         echo "\n";
@@ -2042,4 +2039,41 @@ class Solver
         }
         echo "\n";
     }
+
+    private function printWatches()
+    {
+        echo "\nWatches:\n";
+        foreach ($this->watches as $literalId => $watch) {
+            echo '  '.$this->literalFromId($literalId)."\n";
+            $queue = array(array('    ', $watch));
+
+            while (!empty($queue)) {
+                list($indent, $watch) = array_pop($queue);
+
+                echo $indent.$watch;
+
+                if ($watch) {
+                    echo ' [id='.$watch->getId().',watch1='.$this->literalFromId($watch->watch1).',watch2='.$this->literalFromId($watch->watch2)."]";
+                }
+
+                echo "\n";
+
+                if ($watch && ($watch->next1 == $watch || $watch->next2 == $watch)) {
+                    if ($watch->next1 == $watch) {
+                        echo $indent."    1 *RECURSION*";
+                    }
+                    if ($watch->next2 == $watch) {
+                        echo $indent."    2 *RECURSION*";
+                    }
+                } elseif ($watch && ($watch->next1 || $watch->next2)) {
+                    $indent = str_replace(array('1', '2'), ' ', $indent);
+
+                    array_push($queue, array($indent.'    2 ', $watch->next2));
+                    array_push($queue, array($indent.'    1 ', $watch->next1));
+                }
+            }
+
+            echo "\n";
+        }
+    }
 }

+ 65 - 0
src/Composer/DependencyResolver/SolverProblemsException.php

@@ -0,0 +1,65 @@
+<?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\DependencyResolver;
+
+/**
+ * @author Nils Adermann <naderman@naderman.de>
+ */
+class SolverProblemsException extends \RuntimeException
+{
+    protected $problems;
+
+    public function __construct(array $problems, array $learnedPool)
+    {
+        $message = '';
+        foreach ($problems as $i => $problem) {
+            $message .= '[';
+            foreach ($problem as $why) {
+
+                if (is_int($why) && isset($learnedPool[$why])) {
+                    $rules = $learnedPool[$why];
+                } else {
+                    $rules = $why;
+                }
+
+                if (isset($rules['packages'])) {
+                    $message .= $this->jobToText($rules);
+                } else {
+                    $message .= '(';
+                    foreach ($rules as $rule) {
+                        if ($rule instanceof Rule) {
+                            if ($rule->getType() == RuleSet::TYPE_LEARNED) {
+                                $message .= 'learned: ';
+                            }
+                            $message .= $rule . ', ';
+                        } else {
+                            $message .= 'String(' . $rule . '), ';
+                        }
+                    }
+                    $message .= ')';
+                }
+                $message .= ', ';
+            }
+            $message .= "]\n";
+        }
+
+        parent::__construct($message);
+    }
+
+    public function jobToText($job)
+    {
+        //$output = serialize($job);
+        $output = 'Job(cmd='.$job['cmd'].', target='.$job['packageName'].', packages=['.implode(', ', $job['packages']).'])';
+        return $output;
+    }
+}

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

@@ -125,14 +125,14 @@ class DownloadManager
         $sourceType   = $package->getSourceType();
         $distType     = $package->getDistType();
 
-        if (!($preferSource && $sourceType) && $distType) {
+        if (!$package->isDev() && !($preferSource && $sourceType) && $distType) {
             $package->setInstallationSource('dist');
         } elseif ($sourceType) {
             $package->setInstallationSource('source');
+        } elseif ($package->isDev()) {
+            throw new \InvalidArgumentException('Dev package '.$package.' must have a source specified');
         } else {
-            throw new \InvalidArgumentException(
-                'Package '.$package.' should have source or dist specified'
-            );
+            throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
         }
 
         $fs = new Filesystem();

+ 9 - 1
src/Composer/Factory.php

@@ -71,7 +71,15 @@ class Factory
         $rm = $this->createRepositoryManager($io);
 
         // load default repository unless it's explicitly disabled
-        if (!isset($packageConfig['repositories']['packagist']) || $packageConfig['repositories']['packagist'] !== false) {
+        $loadPackagist = true;
+        if (isset($packageConfig['repositories'])) {
+            foreach ($packageConfig['repositories'] as $repo) {
+                if (isset($repo['packagist']) && $repo['packagist'] === false) {
+                    $loadPackagist = false;
+                }
+            }
+        }
+        if ($loadPackagist) {
             $this->addPackagistRepository($rm);
         }
 

+ 13 - 7
src/Composer/Installer/LibraryInstaller.php

@@ -78,11 +78,9 @@ class LibraryInstaller implements InstallerInterface
      */
     public function install(PackageInterface $package)
     {
+        $this->initializeDirs();
         $downloadPath = $this->getInstallPath($package);
 
-        $this->filesystem->ensureDirectoryExists($this->vendorDir);
-        $this->filesystem->ensureDirectoryExists($this->binDir);
-
         // remove the binaries if it appears the package files are missing
         if (!is_readable($downloadPath) && $this->repository->hasPackage($package)) {
             $this->removeBinaries($package);
@@ -104,16 +102,16 @@ class LibraryInstaller implements InstallerInterface
             throw new \InvalidArgumentException('Package is not installed: '.$initial);
         }
 
+        $this->initializeDirs();
         $downloadPath = $this->getInstallPath($initial);
 
-        $this->filesystem->ensureDirectoryExists($this->vendorDir);
-        $this->filesystem->ensureDirectoryExists($this->binDir);
-
         $this->removeBinaries($initial);
         $this->downloadManager->update($initial, $target, $downloadPath);
         $this->installBinaries($target);
         $this->repository->removePackage($initial);
-        $this->repository->addPackage(clone $target);
+        if (!$this->repository->hasPackage($target)) {
+            $this->repository->addPackage(clone $target);
+        }
     }
 
     /**
@@ -191,6 +189,14 @@ class LibraryInstaller implements InstallerInterface
         }
     }
 
+    protected function initializeDirs()
+    {
+        $this->filesystem->ensureDirectoryExists($this->vendorDir);
+        $this->filesystem->ensureDirectoryExists($this->binDir);
+        $this->vendorDir = realpath($this->vendorDir);
+        $this->binDir = realpath($this->binDir);
+    }
+
     private function generateWindowsProxyCode($bin, $link)
     {
         $binPath = $this->filesystem->findShortestPath($link, $bin);

+ 2 - 0
src/Composer/Package/Loader/ArrayLoader.php

@@ -122,6 +122,8 @@ class ArrayLoader
             $package->setSourceType($config['source']['type']);
             $package->setSourceUrl($config['source']['url']);
             $package->setSourceReference($config['source']['reference']);
+        } elseif ($package->isDev()) {
+            throw new \UnexpectedValueException('Dev package '.$package.' must have a source specified');
         }
 
         if (isset($config['dist'])) {

+ 8 - 5
src/Composer/Package/Loader/RootPackageLoader.php

@@ -38,20 +38,23 @@ class RootPackageLoader extends ArrayLoader
             $config['name'] = '__root__';
         }
         if (!isset($config['version'])) {
-            $config['version'] = '1.0.0-dev';
+            $config['version'] = '1.0.0';
         }
 
         $package = parent::load($config);
 
         if (isset($config['repositories'])) {
-            foreach ($config['repositories'] as $repoName => $repo) {
-                if (false === $repo && 'packagist' === $repoName) {
+            foreach ($config['repositories'] as $index => $repo) {
+                if (isset($repo['packagist']) && $repo['packagist'] === false) {
                     continue;
                 }
                 if (!is_array($repo)) {
-                    throw new \UnexpectedValueException('Repository '.$repoName.' in '.$package->getPrettyName().' '.$package->getVersion().' should be an array, '.gettype($repo).' given');
+                    throw new \UnexpectedValueException('Repository '.$index.' should be an array, '.gettype($repo).' given');
                 }
-                $repository = $this->manager->createRepository(key($repo), current($repo));
+                if (!isset($repo['type'])) {
+                    throw new \UnexpectedValueException('Repository '.$index.' must have a type defined');
+                }
+                $repository = $this->manager->createRepository($repo['type'], $repo);
                 $this->manager->addRepository($repository);
             }
             $package->setRepositories($config['repositories']);

+ 20 - 6
src/Composer/Package/Locker.php

@@ -69,11 +69,7 @@ class Locker
      */
     public function getLockedPackages()
     {
-        if (!$this->isLocked()) {
-            throw new \LogicException('No lockfile found. Unable to read locked packages');
-        }
-
-        $lockList = $this->lockFile->read();
+        $lockList = $this->getLockData();
         $packages = array();
         foreach ($lockList['packages'] as $info) {
             $package = $this->repositoryManager->getLocalRepository()->findPackage($info['package'], $info['version']);
@@ -95,6 +91,15 @@ class Locker
         return $packages;
     }
 
+    public function getLockData()
+    {
+        if (!$this->isLocked()) {
+            throw new \LogicException('No lockfile found. Unable to read locked packages');
+        }
+
+        return $this->lockFile->read();
+    }
+
     /**
      * Locks provided packages into lockfile.
      *
@@ -116,8 +121,17 @@ class Locker
                 ));
             }
 
-            $lock['packages'][] = array('package' => $name, 'version' => $version);
+            $spec = array('package' => $name, 'version' => $version);
+
+            if ($package->isDev()) {
+                $spec['source-reference'] = $package->getSourceReference();
+            }
+
+            $lock['packages'][] = $spec;
         }
+        usort($lock['packages'], function ($a, $b) {
+            return strcmp($a['package'], $b['package']);
+        });
 
         $this->lockFile->write($lock);
     }

+ 11 - 0
src/Composer/Package/MemoryPackage.php

@@ -41,6 +41,7 @@ class MemoryPackage extends BasePackage
     protected $extra = array();
     protected $binaries = array();
     protected $scripts = array();
+    protected $dev;
 
     protected $requires = array();
     protected $conflicts = array();
@@ -63,6 +64,16 @@ class MemoryPackage extends BasePackage
 
         $this->version = $version;
         $this->prettyVersion = $prettyVersion;
+
+        $this->dev = 'dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isDev()
+    {
+        return $this->dev;
     }
 
     /**

+ 7 - 0
src/Composer/Package/PackageInterface.php

@@ -68,6 +68,13 @@ interface PackageInterface
      */
     function matches($name, LinkConstraintInterface $constraint);
 
+    /**
+     * Returns whether the package is a development virtual package or a concrete one
+     *
+     * @return Boolean
+     */
+    function isDev();
+
     /**
      * Returns the package type, e.g. library
      *

+ 8 - 3
src/Composer/Package/Version/VersionParser.php

@@ -34,10 +34,15 @@ class VersionParser
     {
         $version = trim($version);
 
-        if (preg_match('{^(?:master|trunk|default)(?:[.-]?dev)?$}i', $version)) {
+        // match master-like branches
+        if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
             return '9999999-dev';
         }
 
+        if ('dev-' === strtolower(substr($version, 0, 4))) {
+            return strtolower($version);
+        }
+
         // match classical versioning
         if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?'.$this->modifierRegex.'$}i', $version, $matches)) {
             $version = $matches[1]
@@ -53,7 +58,7 @@ class VersionParser
         // add version modifiers if a version was matched
         if (isset($index)) {
             if (!empty($matches[$index])) {
-                $mod = array('{^pl?$}', '{^rc$}');
+                $mod = array('{^pl?$}i', '{^rc$}i');
                 $modNormalized = array('patch', 'RC');
                 $version .= '-'.preg_replace($mod, $modNormalized, strtolower($matches[$index]))
                     . (!empty($matches[$index+1]) ? $matches[$index+1] : '');
@@ -97,7 +102,7 @@ class VersionParser
             return str_replace('x', '9999999', $version).'-dev';
         }
 
-        throw new \UnexpectedValueException('Invalid branch name '.$name);
+        return 'dev-'.$name;
     }
 
     /**

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

@@ -33,7 +33,7 @@ class PackageRepository extends ArrayRepository
      */
     public function __construct(array $config)
     {
-        $this->config = $config;
+        $this->config = $config['package'];
     }
 
     /**

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

@@ -22,6 +22,7 @@ use Composer\Util\StreamContextFactory;
 class PearRepository extends ArrayRepository
 {
     private $url;
+    private $channel;
     private $streamContext;
 
     public function __construct(array $config)
@@ -29,11 +30,14 @@ class PearRepository extends ArrayRepository
         if (!preg_match('{^https?://}', $config['url'])) {
             $config['url'] = 'http://'.$config['url'];
         }
+
         if (!filter_var($config['url'], FILTER_VALIDATE_URL)) {
             throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$config['url']);
         }
 
         $this->url = rtrim($config['url'], '/');
+
+        $this->channel = !empty($config['channel']) ? $config['channel'] : null;
     }
 
     protected function initialize()
@@ -50,6 +54,12 @@ class PearRepository extends ArrayRepository
 
     protected function fetchFromServer()
     {
+        if (!$this->channel) {
+            $channelXML = $this->requestXml($this->url . "/channel.xml");
+            $this->channel = $channelXML->getElementsByTagName("suggestedalias")->item(0)->nodeValue
+                                    ?: $channelXML->getElementsByTagName("name")->item(0)->nodeValue;
+        }
+
         $categoryXML = $this->requestXml($this->url . "/rest/c/categories.xml");
         $categories = $categoryXML->getElementsByTagName("c");
 
@@ -81,6 +91,7 @@ class PearRepository extends ArrayRepository
         $loader = new ArrayLoader();
         foreach ($packages as $package) {
             $packageName = $package->nodeValue;
+            $fullName = 'pear-'.$this->channel.'/'.$packageName;
 
             $packageLink = $package->getAttribute('xlink:href');
             $releaseLink = $this->url . str_replace("/rest/p/", "/rest/r/", $packageLink);
@@ -102,7 +113,7 @@ class PearRepository extends ArrayRepository
                 $pearVersion = $release->getElementsByTagName('v')->item(0)->nodeValue;
 
                 $packageData = array(
-                    'name' => $packageName,
+                    'name' => $fullName,
                     'type' => 'library',
                     'dist' => array('type' => 'pear', 'url' => $this->url.'/get/'.$packageName.'-'.$pearVersion.".tgz"),
                     'version' => $pearVersion,
@@ -220,8 +231,9 @@ class PearRepository extends ArrayRepository
             $package = $information->getElementsByTagName('p')->item(0);
 
             $packageName = $package->getElementsByTagName('n')->item(0)->nodeValue;
+            $fullName = 'pear-'.$this->channel.'/'.$packageName;
             $packageData = array(
-                'name' => $packageName,
+                'name' => $fullName,
                 'type' => 'library'
             );
             $packageKeys = array('l' => 'license', 'd' => 'description');

+ 36 - 10
src/Composer/Repository/Vcs/GitDriver.php

@@ -15,6 +15,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
     protected $branches;
     protected $rootIdentifier;
     protected $infoCache = array();
+    protected $isLocal = false;
 
     public function __construct($url, IOInterface $io, ProcessExecutor $process = null)
     {
@@ -30,10 +31,15 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
     {
         $url = escapeshellarg($this->url);
         $tmpDir = escapeshellarg($this->tmpDir);
-        if (is_dir($this->tmpDir)) {
-            $this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output);
+
+        if (static::isLocalUrl($url)) {
+            $this->isLocal = true;
         } else {
-            $this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output);
+            if (is_dir($this->tmpDir)) {
+                $this->process->execute(sprintf('cd %s && git fetch origin', $tmpDir), $output);
+            } else {
+                $this->process->execute(sprintf('git clone %s %s', $url, $tmpDir), $output);
+            }
         }
 
         $this->getTags();
@@ -47,11 +53,27 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
     {
         if (null === $this->rootIdentifier) {
             $this->rootIdentifier = 'master';
-            $this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output);
-            foreach ($this->process->splitLines($output) as $branch) {
-                if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) {
-                    $this->rootIdentifier = $match[1];
-                    break;
+
+            if ($this->isLocal) {
+                // select currently checked out branch if master is not available
+                $this->process->execute(sprintf('cd %s && git branch --no-color', escapeshellarg($this->tmpDir)), $output);
+                $branches = $this->process->splitLines($output);
+                if (!in_array('* master', $branches)) {
+                    foreach ($branches as $branch) {
+                        if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) {
+                            $this->rootIdentifier = $match[1];
+                            break;
+                        }
+                    }
+                }
+            } else {
+                // try to find a non-master remote HEAD branch
+                $this->process->execute(sprintf('cd %s && git branch --no-color -r', escapeshellarg($this->tmpDir)), $output);
+                foreach ($this->process->splitLines($output) as $branch) {
+                    if ($branch && preg_match('{/HEAD +-> +[^/]+/(\S+)}', $branch, $match)) {
+                        $this->rootIdentifier = $match[1];
+                        break;
+                    }
                 }
             }
         }
@@ -132,7 +154,11 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
         if (null === $this->branches) {
             $branches = array();
 
-            $this->process->execute(sprintf('cd %s && git branch --no-color -rv', escapeshellarg($this->tmpDir)), $output);
+            $this->process->execute(sprintf(
+                'cd %s && git branch --no-color --no-abbrev -v %s',
+                escapeshellarg($this->tmpDir),
+                $this->isLocal ? '' : '-r'
+            ), $output);
             foreach ($this->process->splitLines($output) as $branch) {
                 if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
                     preg_match('{^ *[^/]+/(\S+) *([a-f0-9]+) .*$}', $branch, $match);
@@ -170,7 +196,7 @@ class GitDriver extends VcsDriver implements VcsDriverInterface
         }
 
         // local filesystem
-        if (preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url)) {
+        if (static::isLocalUrl($url)) {
             $process = new ProcessExecutor();
             // check whether there is a git repo in that path
             if ($process->execute(sprintf('cd %s && git show', escapeshellarg($url)), $output) === 0) {

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

@@ -68,4 +68,9 @@ abstract class VcsDriver
         $rfs = new RemoteFilesystem($this->io);
         return $rfs->getContents($this->url, $url, false);
     }
+
+    protected static function isLocalUrl($url)
+    {
+        return (Boolean) preg_match('{^(file://|/|[a-z]:[\\\\/])}i', $url);
+    }
 }

+ 27 - 31
src/Composer/Repository/VcsRepository.php

@@ -76,20 +76,22 @@ class VcsRepository extends ArrayRepository
         }
 
         foreach ($driver->getTags() as $tag => $identifier) {
-            $this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)', false);
+            $msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $tag . '</comment>)';
+            if ($debug) {
+                $this->io->write($msg);
+            } else {
+                $this->io->overwrite($msg, false);
+            }
+
             $parsedTag = $this->validateTag($versionParser, $tag);
             if ($parsedTag && $driver->hasComposerFile($identifier)) {
                 try {
                     $data = $driver->getComposerInformation($identifier);
                 } catch (\Exception $e) {
-                    if (strpos($e->getMessage(), 'JSON Parse Error') !== false) {
-                        if ($debug) {
-                            $this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
-                        }
-                        continue;
-                    } else {
-                        throw $e;
+                    if ($debug) {
+                        $this->io->write('Skipped tag '.$tag.', '.$e->getMessage());
                     }
+                    continue;
                 }
 
                 // manually versioned package
@@ -103,7 +105,7 @@ class VcsRepository extends ArrayRepository
 
                 // make sure tag packages have no -dev flag
                 $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']);
-                $data['version_normalized'] = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']);
+                $data['version_normalized'] = preg_replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);
 
                 // broken package, version doesn't match tag
                 if ($data['version_normalized'] !== $parsedTag) {
@@ -126,39 +128,33 @@ class VcsRepository extends ArrayRepository
         $this->io->overwrite('', false);
 
         foreach ($driver->getBranches() as $branch => $identifier) {
-            $this->io->overwrite('Get composer of <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)', false);
+            $msg = 'Get composer info for <info>' . $this->packageName . '</info> (<comment>' . $branch . '</comment>)';
+            if ($debug) {
+                $this->io->write($msg);
+            } else {
+                $this->io->overwrite($msg, false);
+            }
+
             $parsedBranch = $this->validateBranch($versionParser, $branch);
             if ($driver->hasComposerFile($identifier)) {
                 $data = $driver->getComposerInformation($identifier);
 
-                // manually versioned package
-                if (isset($data['version'])) {
-                    $data['version_normalized'] = $versionParser->normalize($data['version']);
-                } elseif ($parsedBranch) {
-                    // auto-versionned package, read value from branch name
-                    $data['version'] = $branch;
-                    $data['version_normalized'] = $parsedBranch;
-                } else {
+                if (!$parsedBranch) {
                     if ($debug) {
                         $this->io->write('Skipped branch '.$branch.', invalid name and no composer file was found');
                     }
                     continue;
                 }
 
-                // make sure branch packages have a -dev flag
-                $normalizedStableVersion = preg_replace('{[.-]?dev$}i', '', $data['version_normalized']);
-                $data['version'] = preg_replace('{[.-]?dev$}i', '', $data['version']) . '-dev';
-                $data['version_normalized'] = $normalizedStableVersion . '-dev';
+                // branches are always auto-versionned, read value from branch name
+                $data['version'] = $branch;
+                $data['version_normalized'] = $parsedBranch;
 
-                // Skip branches that contain a version that has been tagged already
-                foreach ($this->getPackages() as $package) {
-                    if ($normalizedStableVersion === $package->getVersion()) {
-                        if ($debug) {
-                            $this->io->write('Skipped branch '.$branch.', already tagged');
-                        }
-
-                        continue 2;
-                    }
+                // make sure branch packages have a dev flag
+                if ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
+                    $data['version'] = 'dev-' . $data['version'];
+                } else {
+                    $data['version'] = $data['version'] . '-dev';
                 }
 
                 if ($debug) {

+ 12 - 0
tests/Composer/Test/DependencyResolver/RequestTest.php

@@ -46,4 +46,16 @@ class RequestTest extends TestCase
             ),
             $request->getJobs());
     }
+
+    public function testUpdateAll()
+    {
+        $pool = new Pool;
+        $request = new Request($pool);
+
+        $request->updateAll();
+
+        $this->assertEquals(
+            array(array('cmd' => 'update-all', 'packages' => array())),
+            $request->getJobs());
+    }
 }

+ 3 - 3
tests/Composer/Test/DependencyResolver/RuleSetIteratorTest.php

@@ -27,7 +27,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
                 new Rule(array(), 'job1', null),
                 new Rule(array(), 'job2', null),
             ),
-            RuleSet::TYPE_UPDATE => array(
+            RuleSet::TYPE_FEATURE => array(
                 new Rule(array(), 'update1', null),
             ),
             RuleSet::TYPE_PACKAGE => array(),
@@ -46,7 +46,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
         $expected = array(
             $this->rules[RuleSet::TYPE_JOB][0],
             $this->rules[RuleSet::TYPE_JOB][1],
-            $this->rules[RuleSet::TYPE_UPDATE][0],
+            $this->rules[RuleSet::TYPE_FEATURE][0],
         );
 
         $this->assertEquals($expected, $result);
@@ -64,7 +64,7 @@ class ResultSetIteratorTest extends \PHPUnit_Framework_TestCase
         $expected = array(
             RuleSet::TYPE_JOB,
             RuleSet::TYPE_JOB,
-            RuleSet::TYPE_UPDATE,
+            RuleSet::TYPE_FEATURE,
         );
 
         $this->assertEquals($expected, $result);

+ 9 - 10
tests/Composer/Test/DependencyResolver/RuleSetTest.php

@@ -27,10 +27,9 @@ class RuleSetTest extends TestCase
                 new Rule(array(), 'job1', null),
                 new Rule(array(), 'job2', null),
             ),
-            RuleSet::TYPE_UPDATE => array(
+            RuleSet::TYPE_FEATURE => array(
                 new Rule(array(), 'update1', null),
             ),
-            RuleSet::TYPE_FEATURE => array(),
             RuleSet::TYPE_LEARNED => array(),
             RuleSet::TYPE_CHOICE => array(),
         );
@@ -38,7 +37,7 @@ class RuleSetTest extends TestCase
         $ruleSet = new RuleSet;
 
         $ruleSet->add($rules[RuleSet::TYPE_JOB][0], RuleSet::TYPE_JOB);
-        $ruleSet->add($rules[RuleSet::TYPE_UPDATE][0], RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rules[RuleSet::TYPE_FEATURE][0], RuleSet::TYPE_FEATURE);
         $ruleSet->add($rules[RuleSet::TYPE_JOB][1], RuleSet::TYPE_JOB);
 
         $this->assertEquals($rules, $ruleSet->getRules());
@@ -81,7 +80,7 @@ class RuleSetTest extends TestCase
         $rule1 = new Rule(array(), 'job1', null);
         $rule2 = new Rule(array(), 'job1', null);
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
 
         $iterator = $ruleSet->getIterator();
 
@@ -97,9 +96,9 @@ class RuleSetTest extends TestCase
         $rule2 = new Rule(array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
 
-        $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_UPDATE);
+        $iterator = $ruleSet->getIteratorFor(RuleSet::TYPE_FEATURE);
 
         $this->assertSame($rule2, $iterator->current());
     }
@@ -111,7 +110,7 @@ class RuleSetTest extends TestCase
         $rule2 = new Rule(array(), 'job1', null);
 
         $ruleSet->add($rule1, RuleSet::TYPE_JOB);
-        $ruleSet->add($rule2, RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rule2, RuleSet::TYPE_FEATURE);
 
         $iterator = $ruleSet->getIteratorWithout(RuleSet::TYPE_JOB);
 
@@ -143,7 +142,7 @@ class RuleSetTest extends TestCase
             ->method('equal')
             ->will($this->returnValue(false));
 
-        $ruleSet->add($rule, RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
 
         $this->assertTrue($ruleSet->containsEqual($rule));
         $this->assertFalse($ruleSet->containsEqual($rule2));
@@ -156,9 +155,9 @@ class RuleSetTest extends TestCase
         $literal = new Literal($this->getPackage('foo', '2.1'), true);
         $rule = new Rule(array($literal), 'job1', null);
 
-        $ruleSet->add($rule, RuleSet::TYPE_UPDATE);
+        $ruleSet->add($rule, RuleSet::TYPE_FEATURE);
 
-        $this->assertContains('UPDATE  : (+foo-2.1.0.0)', $ruleSet->__toString());
+        $this->assertContains('FEATURE : (+foo-2.1.0.0)', $ruleSet->__toString());
     }
 
     private function getRuleMock()

+ 155 - 42
tests/Composer/Test/DependencyResolver/SolverTest.php

@@ -19,6 +19,7 @@ use Composer\DependencyResolver\DefaultPolicy;
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\Request;
 use Composer\DependencyResolver\Solver;
+use Composer\DependencyResolver\SolverProblemsException;
 use Composer\Package\Link;
 use Composer\Package\LinkConstraint\VersionConstraint;
 use Composer\Test\TestCase;
@@ -54,13 +55,28 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testInstallNonExistingPackageFails()
+    {
+        $this->repo->addPackage($this->getPackage('A', '1.0'));
+        $this->reposComplete();
+
+        $this->request->install('B');
+
+        try {
+            $transaction = $this->solver->solve($this->request);
+            $this->fail('Unsolvable conflict did not resolve in exception.');
+        } catch (SolverProblemsException $e) {
+            // TODO assert problem properties
+        }
+    }
+
     public function testSolverInstallWithDeps()
     {
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
 
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
 
         $this->reposComplete();
 
@@ -122,12 +138,12 @@ class SolverTest extends TestCase
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
         $this->reposComplete();
 
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0.0.0'), 'requires')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0.0.0'), 'requires')));
 
-        $this->request->install('A', new VersionConstraint('=', '1.0.0.0'));
-        $this->request->install('B', new VersionConstraint('=', '1.1.0.0'));
-        $this->request->update('A', new VersionConstraint('=', '1.0.0.0'));
-        $this->request->update('B', new VersionConstraint('=', '1.0.0.0'));
+        $this->request->install('A', $this->getVersionConstraint('=', '1.0.0.0'));
+        $this->request->install('B', $this->getVersionConstraint('=', '1.1.0.0'));
+        $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
+        $this->request->update('B', $this->getVersionConstraint('=', '1.0.0.0'));
 
         $this->checkSolverResult(array(
             array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB),
@@ -147,6 +163,26 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testSolverUpdateAll()
+    {
+        $this->repoInstalled->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->repoInstalled->addPackage($packageB = $this->getPackage('B', '1.0'));
+        $this->repo->addPackage($newPackageA = $this->getPackage('A', '1.1'));
+        $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
+
+        $packageA->setRequires(array(new Link('A', 'B', null, 'requires')));
+
+        $this->reposComplete();
+
+        $this->request->install('A');
+        $this->request->updateAll();
+
+        $this->checkSolverResult(array(
+            array('job' => 'update', 'from' => $packageB, 'to' => $newPackageB),
+            array('job' => 'update', 'from' => $packageA, 'to' => $newPackageA),
+        ));
+    }
+
     public function testSolverUpdateCurrent()
     {
         $this->repoInstalled->addPackage($this->getPackage('A', '1.0'));
@@ -181,7 +217,7 @@ class SolverTest extends TestCase
         $this->repo->addPackage($this->getPackage('A', '2.0'));
         $this->reposComplete();
 
-        $this->request->install('A', new VersionConstraint('<', '2.0.0.0'));
+        $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
         $this->request->update('A');
 
         $this->checkSolverResult(array(array(
@@ -198,8 +234,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($this->getPackage('A', '2.0'));
         $this->reposComplete();
 
-        $this->request->install('A', new VersionConstraint('<', '2.0.0.0'));
-        $this->request->update('A', new VersionConstraint('=', '1.0.0.0'));
+        $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
+        $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
 
         $this->checkSolverResult(array(array(
             'job' => 'update',
@@ -216,8 +252,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($this->getPackage('A', '2.0'));
         $this->reposComplete();
 
-        $this->request->install('A', new VersionConstraint('<', '2.0.0.0'));
-        $this->request->update('A', new VersionConstraint('=', '1.0.0.0'));
+        $this->request->install('A', $this->getVersionConstraint('<', '2.0.0.0'));
+        $this->request->update('A', $this->getVersionConstraint('=', '1.0.0.0'));
 
         $this->checkSolverResult(array(array(
             'job' => 'update',
@@ -236,7 +272,7 @@ class SolverTest extends TestCase
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
         $this->repo->addPackage($packageC = $this->getPackage('C', '1.1'));
         $this->repo->addPackage($this->getPackage('D', '1.0'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
 
         $this->reposComplete();
 
@@ -258,8 +294,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($middlePackageB = $this->getPackage('B', '1.0'));
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '1.1'));
         $this->repo->addPackage($oldPackageB = $this->getPackage('B', '0.9'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('<', '1.1'), 'requires')));
-        $packageA->setConflicts(array(new Link('A', 'B', new VersionConstraint('<', '1.0'), 'conflicts')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.1'), 'requires')));
+        $packageA->setConflicts(array(new Link('A', 'B', $this->getVersionConstraint('<', '1.0'), 'conflicts')));
 
         $this->reposComplete();
 
@@ -305,8 +341,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
         $this->repo->addPackage($packageB = $this->getPackage('B', '0.8'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageQ->setProvides(array(new Link('Q', 'B', new VersionConstraint('=', '1.0'), 'provides')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageQ->setProvides(array(new Link('Q', 'B', $this->getVersionConstraint('=', '1.0'), 'provides')));
 
         $this->reposComplete();
 
@@ -323,8 +359,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
         $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
 
         $this->reposComplete();
 
@@ -340,8 +376,8 @@ class SolverTest extends TestCase
     {
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
 
         $this->reposComplete();
 
@@ -358,8 +394,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageQ = $this->getPackage('Q', '1.0'));
         $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageQ->setReplaces(array(new Link('Q', 'B', new VersionConstraint('>=', '1.0'), 'replaces')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageQ->setReplaces(array(new Link('Q', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces')));
 
         $this->reposComplete();
 
@@ -376,24 +412,24 @@ class SolverTest extends TestCase
     {
         $this->repo->addPackage($packageX = $this->getPackage('X', '1.0'));
         $packageX->setRequires(array(
-            new Link('X', 'A', new VersionConstraint('>=', '2.0.0.0'), 'requires'),
-            new Link('X', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires')));
+            new Link('X', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires'),
+            new Link('X', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires')));
 
         $this->repo->addPackage($packageA = $this->getPackage('A', '2.0.0'));
         $this->repo->addPackage($newPackageA = $this->getPackage('A', '2.1.0'));
         $this->repo->addPackage($newPackageB = $this->getPackage('B', '2.1.0'));
 
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.0.0.0'), 'requires')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'requires')));
 
         // new package A depends on version of package B that does not exist
         // => new package A is not installable
-        $newPackageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '2.2.0.0'), 'requires')));
+        $newPackageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '2.2.0.0'), 'requires')));
 
         // add a package S replacing both A and B, so that S and B or S and A cannot be simultaneously installed
         // but an alternative option for A and B both exists
         // this creates a more difficult so solve conflict
         $this->repo->addPackage($packageS = $this->getPackage('S', '2.0.0'));
-        $packageS->setReplaces(array(new Link('S', 'A', new VersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', new VersionConstraint('>=', '2.0.0.0'), 'replaces')));
+        $packageS->setReplaces(array(new Link('S', 'A', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces'), new Link('S', 'B', $this->getVersionConstraint('>=', '2.0.0.0'), 'replaces')));
 
         $this->reposComplete();
 
@@ -411,8 +447,8 @@ class SolverTest extends TestCase
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageB1 = $this->getPackage('B', '0.9'));
         $this->repo->addPackage($packageB2 = $this->getPackage('B', '1.1'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageB2->setRequires(array(new Link('B', 'A', new VersionConstraint('>=', '1.0'), 'requires')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('>=', '1.0'), 'requires')));
 
         $this->reposComplete();
 
@@ -426,16 +462,17 @@ class SolverTest extends TestCase
 
     public function testInstallAlternativeWithCircularRequire()
     {
-        $this->markTestIncomplete();
-
         $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
         $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
         $this->repo->addPackage($packageC = $this->getPackage('C', '1.0'));
         $this->repo->addPackage($packageD = $this->getPackage('D', '1.0'));
-        $packageA->setRequires(array(new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageB->setRequires(array(new Link('B', 'Virtual', new VersionConstraint('>=', '1.0'), 'requires')));
-        $packageC->setRequires(array(new Link('C', 'Virtual', new VersionConstraint('==', '1.0'), 'provides')));
-        $packageD->setRequires(array(new Link('D', 'Virtual', new VersionConstraint('==', '1.0'), 'provides')));
+        $packageA->setRequires(array(new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageB->setRequires(array(new Link('B', 'Virtual', $this->getVersionConstraint('>=', '1.0'), 'requires')));
+        $packageC->setProvides(array(new Link('C', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
+        $packageD->setProvides(array(new Link('D', 'Virtual', $this->getVersionConstraint('==', '1.0'), 'provides')));
+
+        $packageC->setRequires(array(new Link('C', 'A', $this->getVersionConstraint('==', '1.0'), 'requires')));
+        $packageD->setRequires(array(new Link('D', 'A', $this->getVersionConstraint('==', '1.0'), 'requires')));
 
         $this->reposComplete();
 
@@ -460,18 +497,18 @@ class SolverTest extends TestCase
         $this->repo->addPackage($packageD2 = $this->getPackage('D', '1.1'));
 
         $packageA->setRequires(array(
-            new Link('A', 'B', new VersionConstraint('>=', '1.0'), 'requires'),
-            new Link('A', 'C', new VersionConstraint('>=', '1.0'), 'requires'),
+            new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'requires'),
+            new Link('A', 'C', $this->getVersionConstraint('>=', '1.0'), 'requires'),
         ));
 
         $packageD->setReplaces(array(
-            new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'),
-            new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'),
+            new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
+            new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
         ));
 
         $packageD2->setReplaces(array(
-            new Link('D', 'B', new VersionConstraint('>=', '1.0'), 'replaces'),
-            new Link('D', 'C', new VersionConstraint('>=', '1.0'), 'replaces'),
+            new Link('D', 'B', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
+            new Link('D', 'C', $this->getVersionConstraint('>=', '1.0'), 'replaces'),
         ));
 
         $this->reposComplete();
@@ -484,6 +521,83 @@ class SolverTest extends TestCase
         ));
     }
 
+    public function testIssue265()
+    {
+        $this->repo->addPackage($packageA1 = $this->getPackage('A', '2.0.999999-dev'));
+        $this->repo->addPackage($packageA2 = $this->getPackage('A', '2.1-dev'));
+        $this->repo->addPackage($packageA3 = $this->getPackage('A', '2.2-dev'));
+        $this->repo->addPackage($packageB1 = $this->getPackage('B', '2.0.10'));
+        $this->repo->addPackage($packageB2 = $this->getPackage('B', '2.0.9'));
+        $this->repo->addPackage($packageC = $this->getPackage('C', '2.0-dev'));
+        $this->repo->addPackage($packageD = $this->getPackage('D', '2.0.9'));
+
+        $packageC->setRequires(array(
+            new Link('C', 'A', $this->getVersionConstraint('>=', '2.0'), 'requires'),
+            new Link('C', 'D', $this->getVersionConstraint('>=', '2.0'), 'requires'),
+        ));
+
+        $packageD->setRequires(array(
+            new Link('D', 'A', $this->getVersionConstraint('>=', '2.1'), 'requires'),
+            new Link('D', 'B', $this->getVersionConstraint('>=', '2.0-dev'), 'requires'),
+        ));
+
+        $packageB1->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires')));
+        $packageB2->setRequires(array(new Link('B', 'A', $this->getVersionConstraint('==', '2.1.0.0-dev'), 'requires')));
+
+        $packageB2->setReplaces(array(new Link('B', 'D', $this->getVersionConstraint('==', '2.0.9.0'), 'replaces')));
+
+        $this->reposComplete();
+
+        $this->request->install('C', $this->getVersionConstraint('==', '2.0.0.0-dev'));
+
+        $this->setExpectedException('Composer\DependencyResolver\SolverProblemsException');
+
+        $this->solver->solve($this->request);
+    }
+
+    public function testConflictResultEmpty()
+    {
+        $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));;
+
+        $packageA->setConflicts(array(
+            new Link('A', 'B', $this->getVersionConstraint('>=', '1.0'), 'conflicts'),
+        ));
+
+        $this->reposComplete();
+
+        $this->request->install('A');
+        $this->request->install('B');
+
+        try {
+            $transaction = $this->solver->solve($this->request);
+            $this->fail('Unsolvable conflict did not resolve in exception.');
+        } catch (SolverProblemsException $e) {
+            // TODO assert problem properties
+        }
+    }
+
+    public function testUnsatisfiableRequires()
+    {
+        $this->repo->addPackage($packageA = $this->getPackage('A', '1.0'));
+        $this->repo->addPackage($packageB = $this->getPackage('B', '1.0'));
+
+        $packageA->setRequires(array(
+            new Link('A', 'B', $this->getVersionConstraint('>=', '2.0'), 'requires'),
+        ));
+
+        $this->reposComplete();
+
+        $this->request->install('A');
+
+        try {
+            $transaction = $this->solver->solve($this->request);
+            $this->fail('Unsolvable conflict did not resolve in exception.');
+        } catch (SolverProblemsException $e) {
+            // TODO assert problem properties
+        }
+    }
+
     protected function reposComplete()
     {
         $this->pool->addRepository($this->repoInstalled);
@@ -513,5 +627,4 @@ class SolverTest extends TestCase
 
         $this->assertEquals($expected, $result);
     }
-
 }

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

@@ -67,9 +67,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->method('getPackages')
             ->will($this->returnValue(array($this->packages[0])));
         $this->repository
-            ->expects($this->once())
+            ->expects($this->exactly(2))
             ->method('hasPackage')
-            ->will($this->returnValue(true));
+            ->will($this->onConsecutiveCalls(true, false));
         $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
 
         $test = $this;
@@ -90,9 +90,9 @@ class InstallerInstallerTest extends \PHPUnit_Framework_TestCase
             ->method('getPackages')
             ->will($this->returnValue(array($this->packages[1])));
         $this->repository
-            ->expects($this->once())
+            ->expects($this->exactly(2))
             ->method('hasPackage')
-            ->will($this->returnValue(true));
+            ->will($this->onConsecutiveCalls(true, false));
         $installer = new InstallerInstallerMock(__DIR__.'/Fixtures/', __DIR__.'/Fixtures/bin', $this->dm, $this->repository, $this->io, $this->im);
 
         $test = $this;

+ 2 - 3
tests/Composer/Test/Installer/LibraryInstallerTest.php

@@ -128,10 +128,9 @@ class LibraryInstallerTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue('package1'));
 
         $this->repository
-            ->expects($this->exactly(2))
+            ->expects($this->exactly(3))
             ->method('hasPackage')
-            ->with($initial)
-            ->will($this->onConsecutiveCalls(true, false));
+            ->will($this->onConsecutiveCalls(true, false, false));
 
         $this->dm
             ->expects($this->once())

+ 10 - 5
tests/Composer/Test/Package/Version/VersionParserTest.php

@@ -49,9 +49,10 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             'parses datetime'   => array('20100102-203040',     '20100102-203040'),
             'parses dt+number'  => array('20100102203040-10',   '20100102203040-10'),
             'parses dt+patch'   => array('20100102-203040-p1',  '20100102-203040-patch1'),
-            'parses master'     => array('master',              '9999999-dev'),
-            'parses trunk'      => array('trunk',               '9999999-dev'),
-            'parses trunk/2'    => array('trunk-dev',           '9999999-dev'),
+            'parses master'     => array('dev-master',          '9999999-dev'),
+            'parses trunk'      => array('dev-trunk',           '9999999-dev'),
+            'parses arbitrary'  => array('dev-feature-foo',     'dev-feature-foo'),
+            'parses arbitrary2' => array('DEV-FOOBAR',          'dev-foobar'),
         );
     }
 
@@ -72,6 +73,7 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             'invalid chars'     => array('a'),
             'invalid type'      => array('1.0.0-meh'),
             'too many bits'     => array('1.0.0.0.0'),
+            'non-dev arbitrary' => array('feature-foo'),
         );
     }
 
@@ -97,6 +99,8 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             'parses long digits/2'  => array('2.4.4',       '2.4.4.9999999-dev'),
             'parses master'         => array('master',      '9999999-dev'),
             'parses trunk'          => array('trunk',       '9999999-dev'),
+            'parses arbitrary'      => array('feature-a',   'dev-feature-a'),
+            'parses arbitrary/2'    => array('foobar',      'dev-foobar'),
         );
     }
 
@@ -121,8 +125,9 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase
             'no op means eq'    => array('1.2.3',       new VersionConstraint('=', '1.2.3.0')),
             'completes version' => array('=1.0',        new VersionConstraint('=', '1.0.0.0')),
             'accepts spaces'    => array('>= 1.2.3',    new VersionConstraint('>=', '1.2.3.0')),
-            'accepts master'    => array('>=master-dev',    new VersionConstraint('>=', '9999999-dev')),
-            'accepts master/2'  => array('master-dev',      new VersionConstraint('=', '9999999-dev')),
+            'accepts master'    => array('>=dev-master',    new VersionConstraint('>=', '9999999-dev')),
+            'accepts master/2'  => array('dev-master',      new VersionConstraint('=', '9999999-dev')),
+            'accepts arbitrary' => array('dev-feature-a',   new VersionConstraint('=', 'dev-feature-a')),
         );
     }
 

+ 140 - 0
tests/Composer/Test/Repository/VcsRepositoryTest.php

@@ -0,0 +1,140 @@
+<?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\Json;
+
+use Symfony\Component\Process\ExecutableFinder;
+use Composer\Package\Dumper\ArrayDumper;
+use Composer\Repository\VcsRepository;
+use Composer\Repository\Vcs\GitDriver;
+use Composer\Util\Filesystem;
+use Composer\Util\ProcessExecutor;
+use Composer\IO\NullIO;
+
+class VcsRepositoryTest extends \PHPUnit_Framework_TestCase
+{
+    private static $gitRepo;
+    private static $skipped;
+
+    public static function setUpBeforeClass()
+    {
+        $oldCwd = getcwd();
+        self::$gitRepo = sys_get_temp_dir() . '/composer-git-'.rand().'/';
+
+        $locator = new ExecutableFinder();
+        if (!$locator->find('git')) {
+            self::$skipped = 'This test needs a git binary in the PATH to be able to run';
+            return;
+        }
+        if (!mkdir(self::$gitRepo) || !chdir(self::$gitRepo)) {
+            self::$skipped = 'Could not create and move into the temp git repo '.self::$gitRepo;
+            return;
+        }
+
+        // init
+        $process = new ProcessExecutor;
+        $process->execute('git init', $null);
+        touch('foo');
+        $process->execute('git add foo', $null);
+        $process->execute('git commit -m init', $null);
+
+        // non-composed tag & branch
+        $process->execute('git tag 0.5.0', $null);
+        $process->execute('git branch oldbranch', $null);
+
+        // add composed tag & master branch
+        $composer = array('name' => 'a/b');
+        file_put_contents('composer.json', json_encode($composer));
+        $process->execute('git add composer.json', $null);
+        $process->execute('git commit -m addcomposer', $null);
+        $process->execute('git tag 0.6.0', $null);
+
+        // add feature-a branch
+        $process->execute('git checkout -b feature-a', $null);
+        file_put_contents('foo', 'bar feature');
+        $process->execute('git add foo', $null);
+        $process->execute('git commit -m change-a', $null);
+
+        // add version to composer.json
+        $process->execute('git checkout master', $null);
+        $composer['version'] = '1.0.0';
+        file_put_contents('composer.json', json_encode($composer));
+        $process->execute('git add composer.json', $null);
+        $process->execute('git commit -m addversion', $null);
+
+        // create tag with wrong version in it
+        $process->execute('git tag 0.9.0', $null);
+        // create tag with correct version in it
+        $process->execute('git tag 1.0.0', $null);
+
+        // add feature-b branch
+        $process->execute('git checkout -b feature-b', $null);
+        file_put_contents('foo', 'baz feature');
+        $process->execute('git add foo', $null);
+        $process->execute('git commit -m change-b', $null);
+
+        // add 1.0 branch
+        $process->execute('git checkout master', $null);
+        $process->execute('git branch 1.0', $null);
+
+        // add 1.0.x branch
+        $process->execute('git branch 1.0.x', $null);
+
+        // update master to 2.0
+        $composer['version'] = '2.0.0';
+        file_put_contents('composer.json', json_encode($composer));
+        $process->execute('git add composer.json', $null);
+        $process->execute('git commit -m bump-version', $null);
+
+        chdir($oldCwd);
+    }
+
+    public function setUp()
+    {
+        if (self::$skipped) {
+            $this->markTestSkipped(self::$skipped);
+        }
+    }
+
+    public static function tearDownAfterClass()
+    {
+        $fs = new Filesystem;
+        $fs->removeDirectory(self::$gitRepo);
+    }
+
+    public function testLoadVersions()
+    {
+        $expected = array(
+            '0.6.0' => true,
+            '1.0.0' => true,
+            '1.0-dev' => true,
+            '1.0.x-dev' => true,
+            'dev-feature-b' => true,
+            'dev-feature-a' => true,
+            'dev-master' => true,
+        );
+
+        $repo = new VcsRepository(array('url' => self::$gitRepo), new NullIO);
+        $packages = $repo->getPackages();
+        $dumper = new ArrayDumper();
+
+        foreach ($packages as $package) {
+            if (isset($expected[$package->getPrettyVersion()])) {
+                unset($expected[$package->getPrettyVersion()]);
+            } else {
+                $this->fail('Unexpected version '.$package->getPrettyVersion().' in '.json_encode($dumper->dump($package)));
+            }
+        }
+
+        $this->assertEmpty($expected, 'Missing versions: '.implode(', ', array_keys($expected)));
+    }
+}

+ 43 - 34
tests/Composer/Test/TestCase.php

@@ -1,34 +1,43 @@
-<?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\Package\Version\VersionParser;
-use Composer\Package\MemoryPackage;
-
-abstract class TestCase extends \PHPUnit_Framework_TestCase
-{
-    private static $versionParser;
-
-    public static function setUpBeforeClass()
-    {
-        if (!self::$versionParser) {
-            self::$versionParser = new VersionParser();
-        }
-    }
-
-    protected function getPackage($name, $version)
-    {
-        $normVersion = self::$versionParser->normalize($version);
-        return new MemoryPackage($name, $normVersion, $version);
-    }
-}
+<?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\Package\Version\VersionParser;
+use Composer\Package\MemoryPackage;
+use Composer\Package\LinkConstraint\VersionConstraint;
+
+abstract class TestCase extends \PHPUnit_Framework_TestCase
+{
+    private static $versionParser;
+
+    public static function setUpBeforeClass()
+    {
+        if (!self::$versionParser) {
+            self::$versionParser = new VersionParser();
+        }
+    }
+
+    protected function getVersionConstraint($operator, $version)
+    {
+        return new VersionConstraint(
+            $operator,
+            self::$versionParser->normalize($version)
+        );
+    }
+
+    protected function getPackage($name, $version)
+    {
+        $normVersion = self::$versionParser->normalize($version);
+        return new MemoryPackage($name, $normVersion, $version);
+    }
+}