diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index d3a130ff3..bebac5355 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -72,18 +72,19 @@ means any version in the `1.0` development branch. It would match `1.0.0`, Version constraints can be specified in a few different ways. -Name | Example | Description --------------- | ------------------------------------------------------------------ | ----------- -Exact version | `1.0.2` | You can specify the exact version of a package. +Name | Example | Description +-------------- | ------------------------------------------------------------------------ | ----------- +Exact version | `1.0.2` | You can specify the exact version of a package. Range | `>=1.0` `>=1.0 <2.0` >=1.0 <1.1 || >=1.2 | By using comparison operators you can specify ranges of valid versions. Valid operators are `>`, `>=`, `<`, `<=`, `!=`.
You can define multiple ranges. Ranges separated by a space (` `) or comma (`,`) will be treated as a **logical AND**. A double pipe (||) will be treated as a **logical OR**. AND has higher precedence than OR. -Hyphen Range | `1.0 - 2.0` | Inclusive set of versions. Partial versions on the right include are completed with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the `2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to `>=1.0.0 <=2.1.0`. -Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0,<1.1`. -Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2,<2.0`. For more details, read the next section below. +Hyphen Range | `1.0 - 2.0` | Inclusive set of versions. Partial versions on the right include are completed with a wildcard. For example `1.0 - 2.0` is equivalent to `>=1.0.0 <2.1` as the `2.0` becomes `2.0.*`. On the other hand `1.0.0 - 2.1.0` is equivalent to `>=1.0.0 <=2.1.0`. +Wildcard | `1.0.*` | You can specify a pattern with a `*` wildcard. `1.0.*` is the equivalent of `>=1.0 <1.1`. +Tilde Operator | `~1.2` | Very useful for projects that follow semantic versioning. `~1.2` is equivalent to `>=1.2 <2.0`. For more details, read the next section below. +Caret Operator | `^1.2.3` | Very useful for projects that follow semantic versioning. `^1.2.3` is equivalent to `>=1.2.3 <2.0`. For more details, read the next section below. -### Next Significant Release (Tilde Operator) +### Next Significant Release (Tilde and Caret Operators) The `~` operator is best explained by example: `~1.2` is equivalent to -`>=1.2,<2.0`, while `~1.2.3` is equivalent to `>=1.2.3,<1.3`. As you can see +`>=1.2 <2.0.0`, while `~1.2.3` is equivalent to `>=1.2.3 <1.3.0`. As you can see it is mostly useful for projects respecting [semantic versioning](http://semver.org/). A common usage would be to mark the minimum minor version you depend on, like `~1.2` (which allows anything up to, but not @@ -91,6 +92,12 @@ including, 2.0). Since in theory there should be no backwards compatibility breaks until 2.0, that works well. Another way of looking at it is that using `~` specifies a minimum version, but allows the last digit specified to go up. +The `^` operator behaves very similarly but it sticks closer to semantic +versioning, and will always allow non-breaking updates. For example `^1.2.3` +is equivalent to `>=1.2.3 <2.0.0` as none of the releases until 2.0 should +break backwards compatibility. For pre-1.0 versions it also acts with safety +in mind and treats `^0.3` as `>=0.3.0 <0.4.0` + > **Note:** Though `2.0-beta.1` is strictly before `2.0`, a version constraint > like `~1.2` would not install it. As said above `~1.2` only means the `.2` > can change but the `1.` part is fixed. diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index c10e85939..70c3de0e5 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -330,6 +330,37 @@ class VersionParser ); } + // match caret constraints + if (preg_match('{^\^'.$versionRegex.'($)}i', $constraint, $matches)) { + // Work out which position in the version we are operating at + if ('0' !== $matches[1] || '' === $matches[2]) { + $position = 1; + } elseif ('0' !== $matches[2] || '' === $matches[3]) { + $position = 2; + } else { + $position = 3; + } + + // Calculate the stability suffix + $stabilitySuffix = ''; + if (empty($matches[5]) && empty($matches[7])) { + $stabilitySuffix .= '-dev'; + } + + $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1)); + $lowerBound = new VersionConstraint('>=', $lowVersion); + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev'; + $upperBound = new VersionConstraint('<', $highVersion); + + return array( + $lowerBound, + $upperBound + ); + } + // match wildcard constraints if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[xX*]$}', $constraint, $matches)) { if (isset($matches[3]) && '' !== $matches[3]) { diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 58b4c9e36..15db1ef69 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -294,6 +294,39 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase ); } + /** + * @dataProvider caretConstraints + */ + public function testParseCaretWildcard($input, $min, $max) + { + $parser = new VersionParser; + if ($min) { + $expected = new MultiConstraint(array($min, $max)); + } else { + $expected = $max; + } + + $this->assertSame((string) $expected, (string) $parser->parseConstraints($input)); + } + + public function caretConstraints() + { + return array( + array('^1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('^0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '1.0.0.0-dev')), + array('^0.0', new VersionConstraint('>=', '0.0.0.0-dev'), new VersionConstraint('<', '0.1.0.0-dev')), + array('^1.2', new VersionConstraint('>=', '1.2.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('^1.2.3-beta.2', new VersionConstraint('>=', '1.2.3.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), + array('^1.2.3.4', new VersionConstraint('>=', '1.2.3.4-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('^1.2.3', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('^0.2.3', new VersionConstraint('>=', '0.2.3.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')), + array('^0.2', new VersionConstraint('>=', '0.2.0.0-dev'), new VersionConstraint('<', '0.3.0.0-dev')), + array('^0.0.3', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')), + array('^0.0.3-alpha', new VersionConstraint('>=', '0.0.3.0-alpha'), new VersionConstraint('<', '0.0.4.0-dev')), + array('^0.0.3-dev', new VersionConstraint('>=', '0.0.3.0-dev'), new VersionConstraint('<', '0.0.4.0-dev')), + ); + } + /** * @dataProvider hyphenConstraints */