From bdaaa863a4cb1499cdfad22693ee339212572d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Mon, 8 May 2017 20:02:04 +0100 Subject: [PATCH 1/7] Offload version parsing to Composer\Semver --- composer.json | 3 +- src/VersionParser.php | 52 +++++++++---------- .../Test/SelfUpdate/VersionParserTest.php | 4 +- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/composer.json b/composer.json index 20d6b39..ebb8d56 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ ], "require": { "php": "^5.6|^7.0", - "padraic/humbug_get_contents": "^1.0" + "padraic/humbug_get_contents": "^1.0", + "composer/semver": "^1.4.2" }, "require-dev": { "phpunit/phpunit": "^5.5|^6.0" diff --git a/src/VersionParser.php b/src/VersionParser.php index 87b1626..9338e14 100644 --- a/src/VersionParser.php +++ b/src/VersionParser.php @@ -12,6 +12,9 @@ namespace Humbug\SelfUpdate; +use Composer\Semver\Semver; +use Composer\Semver\VersionParser as Parser; + class VersionParser { @@ -20,10 +23,15 @@ class VersionParser */ private $versions; + /** + * @var Composer\VersionParser + */ + private $parser; + /** * @var string */ - private $modifier = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'; + private const GIT_DATA_MATCH = '/.*(-\d+-g[[:alnum:]]{7})$/'; /** * @param array $versions @@ -31,6 +39,7 @@ class VersionParser public function __construct(array $versions = array()) { $this->versions = $versions; + $this->parser = new Parser; } /** @@ -159,44 +168,31 @@ private function selectRecentAll() private function findMostRecent(array $candidates) { - $candidate = null; - $tracker = null; - foreach ($candidates as $version) { - if (version_compare($candidate, $version, '<')) { - $candidate = $version; - } - } - return $candidate; + $sorted = Semver::rsort($candidates); + return $sorted[0]; } private function stable($version) { - $version = preg_replace('{#.+$}i', '', $version); - if ($this->development($version)) { - return false; - } - preg_match('{'.$this->modifier.'$}i', strtolower($version), $match); - if (!empty($match[3])) { - return false; - } - if (!empty($match[1])) { - if ('beta' === $match[1] || 'b' === $match[1] - || 'alpha' === $match[1] || 'a' === $match[1] - || 'rc' === $match[1]) { - return false; - } + if ('stable' === Parser::parseStability($this->stripGitHash($version))) { + return true; } - return true; + return false; } private function development($version) { - if ('dev-' === substr($version, 0, 4) || '-dev' === substr($version, -4)) { - return true; - } - if (1 == preg_match("/-\d+-[a-z0-9]{8,}$/", $version)) { + if ('dev' === Parser::parseStability($this->stripGitHash($version))) { return true; } return false; } + + private function stripGitHash($version) + { + if (preg_match(self::GIT_DATA_MATCH, $version, $matches)) { + $version = str_replace($matches[1], '', $version); + } + return $version; + } } diff --git a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php index b867d69..ee7ae5e 100644 --- a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php +++ b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php @@ -147,7 +147,7 @@ public function testIsPreRelease() $this->assertFalse($parser->isPreRelease('1.0.0')); $this->assertTrue($parser->isPreRelease('1.0.0b')); $this->assertFalse($parser->isPreRelease('1.0.0-dev')); - $this->assertFalse($parser->isPreRelease('1.0.0-alpha1-5-g5b46ad8')); + $this->assertTrue($parser->isPreRelease('1.0.0-alpha1-5-g5b46ad8')); } public function testIsUnstable() @@ -165,6 +165,6 @@ public function testIsDevelopment() $this->assertFalse($parser->isDevelopment('1.0.0')); $this->assertFalse($parser->isDevelopment('1.0.0b')); $this->assertTrue($parser->isDevelopment('1.0.0-dev')); - $this->assertTrue($parser->isDevelopment('1.0.0-alpha1-5-g5b46ad8')); + $this->assertFalse($parser->isDevelopment('1.0.0-alpha1-5-g5b46ad8')); } } From 2d75629ed582aed7ce89cb4b98a6d999c403528c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Mon, 8 May 2017 20:25:08 +0100 Subject: [PATCH 2/7] Leverage Semver sorting/normalisation of versions --- src/Updater.php | 20 +++++++- src/VersionParser.php | 20 ++++++-- .../Test/SelfUpdate/VersionParserTest.php | 49 +++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Updater.php b/src/Updater.php index 838d6b0..1e23433 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -12,6 +12,7 @@ namespace Humbug\SelfUpdate; +use Humbug\SelfUpdate\VersionParser; use Humbug\SelfUpdate\Exception\RuntimeException; use Humbug\SelfUpdate\Exception\InvalidArgumentException; use Humbug\SelfUpdate\Exception\FilesystemException; @@ -314,13 +315,28 @@ protected function hasPubKey() return $this->hasPubKey; } + /** + * Compares local and remote versions. If version strings do not follow + * Semver, i.e. hashes, the strings are just checked for equality. + * @return bool + */ protected function newVersionAvailable() { $this->newVersion = $this->strategy->getCurrentRemoteVersion($this); $this->oldVersion = $this->strategy->getCurrentLocalVersion($this); - if (!empty($this->newVersion) && ($this->newVersion !== $this->oldVersion)) { - return true; + try { + if (!empty($this->newVersion) + && !VersionParser::equals($this->newVersion, $this->oldVersion) + ) { + return true; + } + } catch (\UnexpectedValueException $e) { + if (!empty($this->newVersion) + && ($this->newVersion !== $this->oldVersion) + ) { + return true; + } } return false; } diff --git a/src/VersionParser.php b/src/VersionParser.php index 9338e14..42603c1 100644 --- a/src/VersionParser.php +++ b/src/VersionParser.php @@ -121,6 +121,20 @@ public function isDevelopment($version) return $this->development($version); } + /** + * Checks if two version strings are the same normalised version. + * + * @param string + * @param string + * @return bool + */ + public static function equals($version1, $version2) + { + $parser = new Parser; + return $parser->normalize(self::stripGitHash($version1)) + === $parser->normalize(self::stripGitHash($version2)); + } + private function selectRecentStable() { $candidates = array(); @@ -174,7 +188,7 @@ private function findMostRecent(array $candidates) private function stable($version) { - if ('stable' === Parser::parseStability($this->stripGitHash($version))) { + if ('stable' === Parser::parseStability(self::stripGitHash($version))) { return true; } return false; @@ -182,13 +196,13 @@ private function stable($version) private function development($version) { - if ('dev' === Parser::parseStability($this->stripGitHash($version))) { + if ('dev' === Parser::parseStability(self::stripGitHash($version))) { return true; } return false; } - private function stripGitHash($version) + private static function stripGitHash($version) { if (preg_match(self::GIT_DATA_MATCH, $version, $matches)) { $version = str_replace($matches[1], '', $version); diff --git a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php index ee7ae5e..b93975a 100644 --- a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php +++ b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php @@ -167,4 +167,53 @@ public function testIsDevelopment() $this->assertTrue($parser->isDevelopment('1.0.0-dev')); $this->assertFalse($parser->isDevelopment('1.0.0-alpha1-5-g5b46ad8')); } + + public function testEqualsWithSameSemverVersion() + { + $v1 = '1.2.3'; + $v2 = '1.2.3'; + $this->assertTrue(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithDifferentSemverVersion() + { + $v1 = '1.2.3'; + $v2 = '1.2.4'; + $this->assertFalse(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithSameSemverVersionButPrefixed() + { + $v1 = '1.2.3'; + $v2 = 'v1.2.3'; + $this->assertTrue(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithSameSemverVersionButGitData() + { + $v1 = '1.2.3-5-g5b46ad8'; + $v2 = '1.2.3-5-g5b46ad8'; + $this->assertTrue(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithDifferentSemverVersionButGitData() + { + $v1 = '1.2.3-5-g5b46ad8'; + $v2 = '1.2.4-5-g5b46ad8'; + $this->assertFalse(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithSameSemverVersionButStabilityDiffers() + { + $v1 = '1.2.3-alpha'; + $v2 = '1.2.3'; + $this->assertFalse(VersionParser::equals($v1, $v2)); + } + + public function testEqualsWithSameSemverVersionButStabilitySameButNumberedOff() + { + $v1 = '1.2.3-alpha'; + $v2 = '1.2.3-alpha2'; + $this->assertFalse(VersionParser::equals($v1, $v2)); + } } From c24dae93abaed336815c0692d7c10c1918da8f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Mon, 8 May 2017 20:32:56 +0100 Subject: [PATCH 3/7] Constant visibility is PHP 7.1 only... --- src/VersionParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VersionParser.php b/src/VersionParser.php index 42603c1..027b148 100644 --- a/src/VersionParser.php +++ b/src/VersionParser.php @@ -31,7 +31,7 @@ class VersionParser /** * @var string */ - private const GIT_DATA_MATCH = '/.*(-\d+-g[[:alnum:]]{7})$/'; + const GIT_DATA_MATCH = '/.*(-\d+-g[[:alnum:]]{7})$/'; /** * @param array $versions From 64d02324402647cc2d0f834f904d1fba70a492bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 12 May 2017 14:05:42 +0100 Subject: [PATCH 4/7] Throw exception for invalid semantic versioning In the event that a) we are using Github strategy, and b) Composer\Semver reports an invalid version string, throw an exception noting the problem which prevents comparing current version to new remote version. --- src/Updater.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Updater.php b/src/Updater.php index 1a0b6dc..e126d58 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -338,6 +338,16 @@ protected function newVersionAvailable() return true; } } catch (\UnexpectedValueException $e) { + if ($this->getStrategy() instanceof GithubStrategy) { + throw new RuntimeException( + 'The current reported version or the current remote version ' + . 'does not adhere to Semantic Versioning and cannot be compared.' + . PHP_EOL + . sprintf('Current version: %s', $this->oldVersion) + . PHP_EOL + . sprintf('Remote version: %s', $this->newVersion) + ); + } if (!empty($this->newVersion) && ($this->newVersion !== $this->oldVersion) ) { From a4d1d9cb01638573567f5e5270af82eb01ff36c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 12 May 2017 20:39:52 +0100 Subject: [PATCH 5/7] Postfix git-metadata containing versions with -dev to mark dev status --- src/VersionParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VersionParser.php b/src/VersionParser.php index 027b148..59d71e0 100644 --- a/src/VersionParser.php +++ b/src/VersionParser.php @@ -205,7 +205,7 @@ private function development($version) private static function stripGitHash($version) { if (preg_match(self::GIT_DATA_MATCH, $version, $matches)) { - $version = str_replace($matches[1], '', $version); + $version = str_replace($matches[1], '-dev', $version); } return $version; } From 479065a60e335696a38448218d16a9a93901c0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 12 May 2017 20:49:46 +0100 Subject: [PATCH 6/7] Update tests --- tests/Humbug/Test/SelfUpdate/VersionParserTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php index b93975a..c31da03 100644 --- a/tests/Humbug/Test/SelfUpdate/VersionParserTest.php +++ b/tests/Humbug/Test/SelfUpdate/VersionParserTest.php @@ -147,7 +147,7 @@ public function testIsPreRelease() $this->assertFalse($parser->isPreRelease('1.0.0')); $this->assertTrue($parser->isPreRelease('1.0.0b')); $this->assertFalse($parser->isPreRelease('1.0.0-dev')); - $this->assertTrue($parser->isPreRelease('1.0.0-alpha1-5-g5b46ad8')); + $this->assertFalse($parser->isPreRelease('1.0.0-alpha1-5-g5b46ad8')); } public function testIsUnstable() @@ -165,7 +165,7 @@ public function testIsDevelopment() $this->assertFalse($parser->isDevelopment('1.0.0')); $this->assertFalse($parser->isDevelopment('1.0.0b')); $this->assertTrue($parser->isDevelopment('1.0.0-dev')); - $this->assertFalse($parser->isDevelopment('1.0.0-alpha1-5-g5b46ad8')); + $this->assertTrue($parser->isDevelopment('1.0.0-alpha1-5-g5b46ad8')); } public function testEqualsWithSameSemverVersion() From 7feead0389d9a87cadba8f0d2192872b14d5e809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Brady?= Date: Fri, 12 May 2017 21:18:08 +0100 Subject: [PATCH 7/7] Update to README to document limitations with respect to development metadata --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 23f6ac9..e14fb62 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,16 @@ the version string used by Github. This can follow any standard practice with recognisable pre- and postfixes, e.g. `v1.0.3`, `1.0.3`, `1.1`, `1.3rc`, `1.3.2pl2`. +Note: All version strings must follow [SemVer v2.0.0](http://semver.org/) to allow +accurate comparison. The sole exception is that allowance is made for versions pre- +or post-fixed with `dev` to mark development versions. Development versions are +compared solely on the basis of the version string excluding the `dev` label. As +a convenience, versions formatted using, for example, `git describe` (e.g. +`1.0.0alpha.2-26-ge67g3d`) have a `-dev` postfix added (i.e. becoming +`1.0.0alpha.2-dev`). If you frequently update across development versions, it is +strongly recommended to use the SHA-1 or SHA-256 strategies, or implement a custom +strategy that can compare versions based on such metadata. + If you wish to update to a non-stable version, for example where users want to update according to a development track, you can set the stability flag for the Github strategy. By default this is set to `stable` or, in constant form,