Browse Source

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.
Sam Minnee 12 years ago
parent
commit
34a21516c8

+ 62 - 18
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);

+ 6 - 4
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')),