Allow multiline changelog messages, preserve correct order of updates (#4083)

Credit to @GinoPane
This commit is contained in:
Siarhei Karavai 2019-02-19 20:11:43 +03:00 committed by Luke Towers
parent cb981eb82d
commit 9581b23d1e
16 changed files with 234 additions and 222 deletions

View File

@ -94,6 +94,7 @@ class VersionManager
}
$newUpdates = $this->getNewFileVersions($code, $databaseVersion);
foreach ($newUpdates as $version => $details) {
$this->applyPluginUpdate($code, $version, $details);
@ -125,14 +126,7 @@ class VersionManager
*/
protected function applyPluginUpdate($code, $version, $details)
{
if (is_array($details)) {
$comment = array_shift($details);
$scripts = $details;
}
else {
$comment = $details;
$scripts = [];
}
list($comments, $scripts) = $this->extractScriptsAndComments($details);
/*
* Apply scripts, if any
@ -149,12 +143,14 @@ class VersionManager
* Register the comment and update the version
*/
if (!$this->hasDatabaseHistory($code, $version)) {
$this->applyDatabaseComment($code, $version, $comment);
foreach ($comments as $comment) {
$this->applyDatabaseComment($code, $version, $comment);
$this->note(sprintf('- <info>v%s: </info> %s', $version, $comment));
}
}
$this->setDatabaseVersion($code, $version);
$this->note(sprintf('- <info>v%s: </info> %s', $version, $comment));
}
/**
@ -393,6 +389,7 @@ class VersionManager
if (!File::isFile($updateFile)) {
$this->note('- <error>v' . $version . ': Migration file "' . $script . '" not found</error>');
return;
}
$this->updater->setUp($updateFile);
@ -524,4 +521,29 @@ class VersionManager
return $this;
}
/**
* @param $details
*
* @return array
*/
protected function extractScriptsAndComments($details)
{
if (is_array($details)) {
$fileNamePattern = '/^[a-z_\-0-9]*\.php$/i';
$comments = array_values(array_filter($details, function ($detail) use ($fileNamePattern) {
return !preg_match($fileNamePattern, $detail);
}));
$scripts = array_values(array_filter($details, function ($detail) use ($fileNamePattern) {
return preg_match($fileNamePattern, $detail);
}));
} else {
$comments = (array)$details;
$scripts = [];
}
return array($comments, $scripts);
}
}

View File

@ -181,17 +181,27 @@ class Updates extends Controller
$contents = [];
try {
$updates = Yaml::parseFile($path.'/'.$filename);
$updates = is_array($updates) ? array_reverse($updates) : [];
$updates = (array) Yaml::parseFile($path.'/'.$filename);
foreach ($updates as $version => $details) {
$contents[$version] = is_array($details)
? array_shift($details)
: $details;
if (!is_array($details)) {
$details = (array)$details;
}
//Filter out update scripts
$details = array_filter($details, function($string) use ($path) {
return !preg_match('/^[a-z_\-0-9]*\.php$/i', $string) || !File::exists($path . '/updates/' . $string);
});
$contents[$version] = $details;
}
}
catch (Exception $ex) {}
uksort($contents, function ($a, $b) {
return version_compare($b, $a);
});
return $contents;
}

View File

@ -76,10 +76,12 @@
<div class="plugin-details-content">
<?php if ($changelog): ?>
<dl>
<?php foreach ($changelog as $version => $comment): ?>
<dt><?= e($version) ?></dt>
<dd><?= e($comment) ?></dd>
<?php endforeach ?>
<?php foreach ($changelog as $version => $comments): ?>
<?php foreach ($comments as $index => $comment): ?>
<dt><?= !$index ? e($version): '' ?></dt>
<dd><?= e($comment) ?></dd>
<?php endforeach; ?>
<?php endforeach; ?>
</dl>
<?php else: ?>
<p><?= e(trans('system::lang.updates.details_changelog_missing')) ?></p>

View File

@ -19,4 +19,34 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
return $app;
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
}

View File

@ -1,12 +1,18 @@
1.0.5:
- Create blog settings table
- Another update message
- Yet one more update message
- create_blog_settings_table.php
1.0.4: Another fix
1.0.3: Bug fix update that uses no scripts
1.0.2:
- Create blog post comments table
- Multiple update messages are allowed
- create_comments_table.php
1.0.1:
- Added some upgrade file and some seeding
- some_upgrade_file.php
- some_seeding_file.php
1.0.2:
- Create blog post comments table
- create_comments_table.php
1.0.3: Bug fix update that uses no scripts
1.0.4: Another fix
1.0.5:
- Create blog settings table
- create_blog_settings_table.php

View File

@ -26,19 +26,6 @@ class ExampleExportModel extends ExportModel
class ExportModelTest extends TestCase
{
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
//
// Tests
//

View File

@ -17,19 +17,6 @@ class ExampleImportModel extends ImportModel
class ImportModelTest extends TestCase
{
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
//
// Tests
//

View File

@ -11,37 +11,6 @@ use October\Rain\Exception\SystemException;
class CmsExceptionTest extends TestCase
{
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
//
// Tests
//

View File

@ -12,37 +12,6 @@ class CombineAssetsTest extends TestCase
CombineAssets::resetCache();
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
//
// Tests
//

View File

@ -12,37 +12,6 @@ class MarkupManagerTest extends TestCase
include_once base_path().'/tests/fixtures/plugins/october/tester/Plugin.php';
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
//
// Tests
//

View File

@ -12,37 +12,6 @@ class PluginManagerTest extends TestCase
include_once base_path().'/tests/fixtures/plugins/october/tester/Plugin.php';
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
//
// Tests
//

View File

@ -0,0 +1,51 @@
<?php
use System\Controllers\Updates;
class UpdatesControllerTest extends TestCase
{
//
// Tests
//
public function testGetPluginVersionFile()
{
$controller = $this->getMockBuilder(Updates::class)->disableOriginalConstructor()->getMock();
$expectedVersions = [
'1.0.5' => [
'Create blog settings table',
'Another update message',
'Yet one more update message'
],
'1.0.4' => [
'Another fix'
],
'1.0.3' => [
'Bug fix update that uses no scripts'
],
'1.0.2' => [
'Create blog post comments table',
'Multiple update messages are allowed'
],
'1.0.1' => [
'Added some upgrade file and some seeding',
'some_upgrade_file.php', //does not exist
'some_seeding_file.php' //does not exist
]
];
$versions = self::callProtectedMethod(
$controller,
'getPluginVersionFile',
[
base_path().'/tests/fixtures/plugins/october/tester/',
'updates/version.yaml'
]
);
$this->assertNotNull($versions);
$this->assertEquals($expectedVersions, $versions);
}
}

View File

@ -14,37 +14,6 @@ class VersionManagerTest extends TestCase
include_once base_path().'/tests/fixtures/plugins/october/noupdates/Plugin.php';
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
public static function getProtectedProperty($object, $name)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
public static function setProtectedProperty($object, $name, $value)
{
$className = get_class($object);
$class = new ReflectionClass($className);
$property = $class->getProperty($name);
$property->setAccessible(true);
return $property->setValue($object, $value);
}
//
// Tests
//
@ -119,4 +88,89 @@ class VersionManagerTest extends TestCase
$this->assertArrayHasKey('1.0.5', $result);
}
/**
* @dataProvider versionInfoProvider
*
* @param $versionInfo
* @param $expectedComments
* @param $expectedScripts
*/
public function testExtractScriptsAndComments($versionInfo, $expectedComments, $expectedScripts)
{
$manager = VersionManager::instance();
list($comments, $scripts) = self::callProtectedMethod($manager, 'extractScriptsAndComments', [$versionInfo]);
$this->assertInternalType('array', $comments);
$this->assertInternalType('array', $scripts);
$this->assertEquals($expectedComments, $comments);
$this->assertEquals($expectedScripts, $scripts);
}
public function versionInfoProvider()
{
return [
[
'A single update comment string',
[
'A single update comment string'
],
[]
],
[
[
'A classic update comment string followed by script',
'update_script.php'
],
[
'A classic update comment string followed by script'
],
[
'update_script.php'
]
],
[
[
'scripts_can_go_first.php',
'An update comment string after the script',
],
[
'An update comment string after the script'
],
[
'scripts_can_go_first.php'
]
],
[
[
'scripts_can_go_first.php',
'An update comment string after the script',
'scripts_can_go_anywhere.php',
],
[
'An update comment string after the script'
],
[
'scripts_can_go_first.php',
'scripts_can_go_anywhere.php'
]
],
[
[
'scripts_can_go_first.php',
'The first update comment',
'scripts_can_go_anywhere.php',
'The second update comment',
],
[
'The first update comment',
'The second update comment'
],
[
'scripts_can_go_first.php',
'scripts_can_go_anywhere.php'
]
]
];
}
}

View File

@ -16,19 +16,6 @@ class AssetMakerTest extends TestCase
$this->stub = new AssetMakerStub();
}
//
// Helpers
//
protected static function callProtectedMethod($object, $name, $params = [])
{
$className = get_class($object);
$class = new ReflectionClass($className);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($object, $params);
}
//
// Tests
//