From 34a21516c8af4e52033b2fe68f6c5716603fb7ac Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Wed, 8 May 2013 18:03:29 +1200 Subject: [PATCH 1/5] Fix tilde constraints without a stability suffix to work like wildcard constraints. With this fix, a tilde constraint such as ~3.1 won't match unstable versions of 3.1, but a wildcard constraint such as 3.1.* would. This seems like a confusing inconsistency, and so I have corrected it. --- .../Package/Version/VersionParser.php | 80 ++++++++++++++----- .../Package/Version/VersionParserTest.php | 10 ++- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index ba9b460b0..a6a53fcfb 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -269,33 +269,49 @@ class VersionParser return array(new EmptyConstraint); } + // match tilde constraints + // like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous + // version, to ensure that unstable instances of the current version are allowed. + // however, if a stability suffix is added to the constraint, then a >= match on the current version is + // used instead if (preg_match('{^~(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex.'?$}i', $constraint, $matches)) { - if (isset($matches[4]) && '' !== $matches[4]) { - $highVersion = $matches[1] . '.' . $matches[2] . '.' . ($matches[3] + 1) . '.0-dev'; - $lowVersion = $matches[1] . '.' . $matches[2] . '.' . $matches[3]. '.' . $matches[4]; - } elseif (isset($matches[3]) && '' !== $matches[3]) { - $highVersion = $matches[1] . '.' . ($matches[2] + 1) . '.0.0-dev'; - $lowVersion = $matches[1] . '.' . $matches[2] . '.' . $matches[3]. '.0'; - } else { - $highVersion = ($matches[1] + 1) . '.0.0.0-dev'; - if (isset($matches[2]) && '' !== $matches[2]) { - $lowVersion = $matches[1] . '.' . $matches[2] . '.0.0'; - } else { - $lowVersion = $matches[1] . '.0.0.0'; - } - } + // Work out which position in the version we are operating at + if (isset($matches[4]) && '' !== $matches[4]) $position = 4; + else if (isset($matches[3]) && '' !== $matches[3]) $position = 3; + else if (isset($matches[2]) && '' !== $matches[2]) $position = 2; + else $position = 1; + + // Calculate the stability suffix + $stabilitySuffix = ''; if (!empty($matches[5])) { - $lowVersion .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : ''); + $stabilitySuffix .= '-' . $this->expandStability($matches[5]) . (!empty($matches[6]) ? $matches[6] : ''); } if (!empty($matches[7])) { - $lowVersion .= '-dev'; + $stabilitySuffix .= '-dev'; } + // If we don't have a stability suffix, the lower bound is "> the previous version" + if ($stabilitySuffix == '') { + $lowVersion = $this->manipulateVersionString($matches, $position,-1,'9999999'); + $lowerBound = new VersionConstraint('>', $lowVersion); + + // If we have a stability suffix, then our comparison is ">= this version" + } else { + $lowVersion = $this->manipulateVersionString($matches,$position,0); + $lowerBound = new VersionConstraint('>=', $lowVersion . $stabilitySuffix); + } + + // For upper bound, we increment the position of one more significance, + // but highPosition = 0 would be illegal + $highPosition = max(1,$position-1); + $highVersion = $this->manipulateVersionString($matches,$highPosition,1).'-dev'; + $upperBound = new VersionConstraint('<', $highVersion); + return array( - new VersionConstraint('>=', $lowVersion), - new VersionConstraint('<', $highVersion), + $lowerBound, + $upperBound ); } @@ -355,6 +371,34 @@ class VersionParser throw new \UnexpectedValueException($message); } + /** + * Increment, decrement, or simply pad a version number. + * + * Support function for {@link parseConstraint()} + * + * @param array $matches Array with version parts in array indexes 1,2,3,4 + * @param int $position 1,2,3,4 - which segment of the version to decrement + * @param string $pad The string to pad version parts after $position + * @return string The new version + */ + private function manipulateVersionString($matches, $position, $increment = 0, $pad = '0') { + for($i = 4; $i>0; $i--) { + if($i > $position) { + $matches[$i] = $pad; + + } else if(($i == $position) && $increment) { + $matches[$i] += $increment; + // If $matches[$i] was 0, carry the decrement + if($matches[$i] < 0) { + $matches[$i] = $pad; + $position--; + } + } + } + + return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4]; + } + private function expandStability($stability) { $stability = strtolower($stability); diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index c46d47fb9..6d3c1a9de 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -265,10 +265,12 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase public function tildeConstraints() { return array( - array('~1', new VersionConstraint('>=', '1.0.0.0'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2', new VersionConstraint('>=', '1.2.0.0'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2.3', new VersionConstraint('>=', '1.2.3.0'), new VersionConstraint('<', '1.3.0.0-dev')), - array('~1.2.3.4', new VersionConstraint('>=', '1.2.3.4'), new VersionConstraint('<', '1.2.4.0-dev')), + array('~1', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), + array('~1.0', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), + array('~1.0.0', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '1.1.0.0-dev')), + array('~1.2', new VersionConstraint('>', '1.1.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), + array('~1.2.3', new VersionConstraint('>', '1.2.2.9999999'), new VersionConstraint('<', '1.3.0.0-dev')), + array('~1.2.3.4', new VersionConstraint('>', '1.2.3.3'), new VersionConstraint('<', '1.2.4.0-dev')), array('~1.2-beta',new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.0.0.0-dev')), array('~1.2-b2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), array('~1.2-BETA2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), From b25457a8c12902689ef4948bc4d6997bddf2a222 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Thu, 9 May 2013 11:30:39 +1200 Subject: [PATCH 2/5] Refactor wildcard matching to use manipulateVersionString --- .../Package/Version/VersionParser.php | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index a6a53fcfb..cbae7939d 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -317,33 +317,21 @@ class VersionParser // match wildcard constraints if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[x*]$}', $constraint, $matches)) { - if (isset($matches[3])) { - $highVersion = $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.9999999'; - if ($matches[3] === '0') { - $lowVersion = $matches[1] . '.' . ($matches[2] - 1) . '.9999999.9999999'; - } else { - $lowVersion = $matches[1] . '.' . $matches[2] . '.' . ($matches[3] - 1). '.9999999'; - } - } elseif (isset($matches[2])) { - $highVersion = $matches[1] . '.' . $matches[2] . '.9999999.9999999'; - if ($matches[2] === '0') { - $lowVersion = ($matches[1] - 1) . '.9999999.9999999.9999999'; - } else { - $lowVersion = $matches[1] . '.' . ($matches[2] - 1) . '.9999999.9999999'; - } + if (isset($matches[3]) && '' !== $matches[3]) $position = 3; + else if (isset($matches[2]) && '' !== $matches[2]) $position = 2; + else $position = 1; + + $highVersion = $this->manipulateVersionString($matches,$position,0,'9999999'); + $lowVersion = $this->manipulateVersionString($matches,$position,-1,'9999999'); + + if($lowVersion === null) { + return array(new VersionConstraint('<', $highVersion)); } else { - $highVersion = $matches[1] . '.9999999.9999999.9999999'; - if ($matches[1] === '0') { - return array(new VersionConstraint('<', $highVersion)); - } else { - $lowVersion = ($matches[1] - 1) . '.9999999.9999999.9999999'; - } + return array( + new VersionConstraint('>', $lowVersion), + new VersionConstraint('<', $highVersion), + ); } - - return array( - new VersionConstraint('>', $lowVersion), - new VersionConstraint('<', $highVersion), - ); } // match operators constraints @@ -392,6 +380,9 @@ class VersionParser if($matches[$i] < 0) { $matches[$i] = $pad; $position--; + + // Return null on a carry overflow + if($i == 1) return null; } } } From be9aae5bab0dbbdee75a734c565d767bb056bbc7 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Sun, 12 May 2013 16:36:34 +1200 Subject: [PATCH 3/5] Formatting updates for PSR-2 compliance. --- .../Package/Version/VersionParser.php | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index cbae7939d..5cd05f512 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -277,10 +277,15 @@ class VersionParser if (preg_match('{^~(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'.self::$modifierRegex.'?$}i', $constraint, $matches)) { // Work out which position in the version we are operating at - if (isset($matches[4]) && '' !== $matches[4]) $position = 4; - else if (isset($matches[3]) && '' !== $matches[3]) $position = 3; - else if (isset($matches[2]) && '' !== $matches[2]) $position = 2; - else $position = 1; + if (isset($matches[4]) && '' !== $matches[4]) { + $position = 4; + } elseif (isset($matches[3]) && '' !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } // Calculate the stability suffix $stabilitySuffix = ''; @@ -294,19 +299,19 @@ class VersionParser // If we don't have a stability suffix, the lower bound is "> the previous version" if ($stabilitySuffix == '') { - $lowVersion = $this->manipulateVersionString($matches, $position,-1,'9999999'); + $lowVersion = $this->manipulateVersionString($matches, $position, -1, '9999999'); $lowerBound = new VersionConstraint('>', $lowVersion); // If we have a stability suffix, then our comparison is ">= this version" } else { - $lowVersion = $this->manipulateVersionString($matches,$position,0); + $lowVersion = $this->manipulateVersionString($matches, $position, 0); $lowerBound = new VersionConstraint('>=', $lowVersion . $stabilitySuffix); } // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal - $highPosition = max(1,$position-1); - $highVersion = $this->manipulateVersionString($matches,$highPosition,1).'-dev'; + $highPosition = max(1, $position - 1); + $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev'; $upperBound = new VersionConstraint('<', $highVersion); return array( @@ -317,12 +322,16 @@ class VersionParser // match wildcard constraints if (preg_match('{^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[x*]$}', $constraint, $matches)) { - if (isset($matches[3]) && '' !== $matches[3]) $position = 3; - else if (isset($matches[2]) && '' !== $matches[2]) $position = 2; - else $position = 1; + if (isset($matches[3]) && '' !== $matches[3]) { + $position = 3; + } elseif (isset($matches[2]) && '' !== $matches[2]) { + $position = 2; + } else { + $position = 1; + } - $highVersion = $this->manipulateVersionString($matches,$position,0,'9999999'); - $lowVersion = $this->manipulateVersionString($matches,$position,-1,'9999999'); + $highVersion = $this->manipulateVersionString($matches, $position, 0, '9999999'); + $lowVersion = $this->manipulateVersionString($matches, $position, -1, '9999999'); if($lowVersion === null) { return array(new VersionConstraint('<', $highVersion)); @@ -353,7 +362,7 @@ class VersionParser $message = 'Could not parse version constraint '.$constraint; if (isset($e)) { - $message .= ': '.$e->getMessage(); + $message .= ': '. $e->getMessage(); } throw new \UnexpectedValueException($message); From 95b4348afa2ee335d9dc1f9e2fecf579b49f7ddf Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 20 May 2013 19:02:40 +1200 Subject: [PATCH 4/5] Fixed lower bound of tilde and wildcard matches. The lower bound of ~1.2 and 1.2.* style version requirements now uses >= 1.2.0.0-dev instead of > 1.1.9999999.9999999. This is more straightforward to read, and behaves equivalently. --- .../Package/Version/VersionParser.php | 19 +++++---------- .../Package/Version/VersionParserTest.php | 24 +++++++++---------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 5cd05f512..1a412dffd 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -297,16 +297,9 @@ class VersionParser $stabilitySuffix .= '-dev'; } - // If we don't have a stability suffix, the lower bound is "> the previous version" - if ($stabilitySuffix == '') { - $lowVersion = $this->manipulateVersionString($matches, $position, -1, '9999999'); - $lowerBound = new VersionConstraint('>', $lowVersion); - - // If we have a stability suffix, then our comparison is ">= this version" - } else { - $lowVersion = $this->manipulateVersionString($matches, $position, 0); - $lowerBound = new VersionConstraint('>=', $lowVersion . $stabilitySuffix); - } + if(!$stabilitySuffix) $stabilitySuffix = "-dev"; + $lowVersion = $this->manipulateVersionString($matches, $position, 0) . $stabilitySuffix; + $lowerBound = new VersionConstraint('>=', $lowVersion); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal @@ -330,14 +323,14 @@ class VersionParser $position = 1; } + $lowVersion = $this->manipulateVersionString($matches, $position) . "-dev"; $highVersion = $this->manipulateVersionString($matches, $position, 0, '9999999'); - $lowVersion = $this->manipulateVersionString($matches, $position, -1, '9999999'); - if($lowVersion === null) { + if($lowVersion === "0.0.0.0-dev") { return array(new VersionConstraint('<', $highVersion)); } else { return array( - new VersionConstraint('>', $lowVersion), + new VersionConstraint('>=', $lowVersion), new VersionConstraint('<', $highVersion), ); } diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 6d3c1a9de..5587050b8 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -237,12 +237,12 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase public function wildcardConstraints() { return array( - array('2.*', new VersionConstraint('>', '1.9999999.9999999.9999999'), new VersionConstraint('<', '2.9999999.9999999.9999999')), - array('20.*', new VersionConstraint('>', '19.9999999.9999999.9999999'), new VersionConstraint('<', '20.9999999.9999999.9999999')), - array('2.0.*', new VersionConstraint('>', '1.9999999.9999999.9999999'), new VersionConstraint('<', '2.0.9999999.9999999')), - array('2.2.x', new VersionConstraint('>', '2.1.9999999.9999999'), new VersionConstraint('<', '2.2.9999999.9999999')), - array('2.10.x', new VersionConstraint('>', '2.9.9999999.9999999'), new VersionConstraint('<', '2.10.9999999.9999999')), - array('2.1.3.*', new VersionConstraint('>', '2.1.2.9999999'), new VersionConstraint('<', '2.1.3.9999999')), + array('2.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.9999999.9999999.9999999')), + array('20.*', new VersionConstraint('>=', '20.0.0.0-dev'), new VersionConstraint('<', '20.9999999.9999999.9999999')), + array('2.0.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.0.9999999.9999999')), + array('2.2.x', new VersionConstraint('>=', '2.2.0.0-dev'), new VersionConstraint('<', '2.2.9999999.9999999')), + array('2.10.x', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.10.9999999.9999999')), + array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0-dev'), new VersionConstraint('<', '2.1.3.9999999')), array('0.*', null, new VersionConstraint('<', '0.9999999.9999999.9999999')), ); } @@ -265,12 +265,12 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase public function tildeConstraints() { return array( - array('~1', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.0', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.0.0', new VersionConstraint('>', '0.9999999.9999999.9999999'), new VersionConstraint('<', '1.1.0.0-dev')), - array('~1.2', new VersionConstraint('>', '1.1.9999999.9999999'), new VersionConstraint('<', '2.0.0.0-dev')), - array('~1.2.3', new VersionConstraint('>', '1.2.2.9999999'), new VersionConstraint('<', '1.3.0.0-dev')), - array('~1.2.3.4', new VersionConstraint('>', '1.2.3.3'), new VersionConstraint('<', '1.2.4.0-dev')), + array('~1', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('~1.0', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '2.0.0.0-dev')), + array('~1.0.0', new VersionConstraint('>=', '1.0.0.0-dev'), new VersionConstraint('<', '1.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', new VersionConstraint('>=', '1.2.3.0-dev'), new VersionConstraint('<', '1.3.0.0-dev')), + array('~1.2.3.4', new VersionConstraint('>=', '1.2.3.4-dev'), new VersionConstraint('<', '1.2.4.0-dev')), array('~1.2-beta',new VersionConstraint('>=', '1.2.0.0-beta'), new VersionConstraint('<', '2.0.0.0-dev')), array('~1.2-b2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), array('~1.2-BETA2', new VersionConstraint('>=', '1.2.0.0-beta2'), new VersionConstraint('<', '2.0.0.0-dev')), From 324ef0f9bb210c626929c93d75233f6c148ce773 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Mon, 20 May 2013 19:10:04 +1200 Subject: [PATCH 5/5] Fixed upper bound of wildcard matches. Follow-on from 95b4348afa2ee335d9dc1f9e2fecf579b49f7ddf --- src/Composer/Package/Version/VersionParser.php | 2 +- .../Test/Package/Version/VersionParserTest.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Composer/Package/Version/VersionParser.php b/src/Composer/Package/Version/VersionParser.php index 1a412dffd..93c10d80e 100644 --- a/src/Composer/Package/Version/VersionParser.php +++ b/src/Composer/Package/Version/VersionParser.php @@ -324,7 +324,7 @@ class VersionParser } $lowVersion = $this->manipulateVersionString($matches, $position) . "-dev"; - $highVersion = $this->manipulateVersionString($matches, $position, 0, '9999999'); + $highVersion = $this->manipulateVersionString($matches, $position, 1) . "-dev"; if($lowVersion === "0.0.0.0-dev") { return array(new VersionConstraint('<', $highVersion)); diff --git a/tests/Composer/Test/Package/Version/VersionParserTest.php b/tests/Composer/Test/Package/Version/VersionParserTest.php index 5587050b8..19fd9f16f 100644 --- a/tests/Composer/Test/Package/Version/VersionParserTest.php +++ b/tests/Composer/Test/Package/Version/VersionParserTest.php @@ -237,13 +237,13 @@ class VersionParserTest extends \PHPUnit_Framework_TestCase public function wildcardConstraints() { return array( - array('2.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.9999999.9999999.9999999')), - array('20.*', new VersionConstraint('>=', '20.0.0.0-dev'), new VersionConstraint('<', '20.9999999.9999999.9999999')), - array('2.0.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.0.9999999.9999999')), - array('2.2.x', new VersionConstraint('>=', '2.2.0.0-dev'), new VersionConstraint('<', '2.2.9999999.9999999')), - array('2.10.x', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.10.9999999.9999999')), - array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0-dev'), new VersionConstraint('<', '2.1.3.9999999')), - array('0.*', null, new VersionConstraint('<', '0.9999999.9999999.9999999')), + array('2.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '3.0.0.0-dev')), + array('20.*', new VersionConstraint('>=', '20.0.0.0-dev'), new VersionConstraint('<', '21.0.0.0-dev')), + array('2.0.*', new VersionConstraint('>=', '2.0.0.0-dev'), new VersionConstraint('<', '2.1.0.0-dev')), + array('2.2.x', new VersionConstraint('>=', '2.2.0.0-dev'), new VersionConstraint('<', '2.3.0.0-dev')), + array('2.10.x', new VersionConstraint('>=', '2.10.0.0-dev'), new VersionConstraint('<', '2.11.0.0-dev')), + array('2.1.3.*', new VersionConstraint('>=', '2.1.3.0-dev'), new VersionConstraint('<', '2.1.4.0-dev')), + array('0.*', null, new VersionConstraint('<', '1.0.0.0-dev')), ); }