From f80c99069cecf57ab888575d44f0ddac5d13ae0f Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 30 Apr 2024 12:46:13 +1200 Subject: [PATCH] NEW Use supported modules repo for branch-based logic --- .editorconfig | 16 +++ .gitignore | 3 + action.yml | 23 ++++- branches.php | 10 +- composer.json | 14 +++ funcs.php | 182 ++++------------------------------ tests/BranchesTest.php | 220 ++++++++++++++++++++++++++++++++++++----- 7 files changed, 272 insertions(+), 196 deletions(-) create mode 100644 .editorconfig create mode 100644 composer.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..50eff41 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# For more information about the properties used in +# this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 81fdeb9..9c25456 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.json +!composer.json .phpunit.result.cache +vendor/ +composer.lock diff --git a/action.yml b/action.yml index 8054b4f..4687a57 100644 --- a/action.yml +++ b/action.yml @@ -9,6 +9,23 @@ runs: uses: shivammathur/setup-php@1a18b2267f80291a81ca1d33e7c851fe09e7dfc4 # v2.22.0 with: php-version: '8.1' + tools: composer:v2 + + # This is shared between runs, not just jobs. It means the first time the repo runs the job it'll + # need to download requirements for the first time, after that it will be plenty quick + # https://docs.github.com/en/actions/advanced-guides/caching-dependencies-to-speed-up-workflows + - name: Enable shared composer cache + uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # @v3.3.1 + with: + path: ~/.cache/composer + key: shared-composer-cache + + # Install composer dependencies for this action itself + - name: Composer + run: | + cd ${{ github.action_path }} + composer install + cd - - name: Determine if should merge-up id: determine @@ -17,10 +34,6 @@ runs: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_REF_NAME: ${{ github.ref_name }} run: | - # The minimum cms major with commercial support - configured at a global level - # Change this when major version support changes - MINIMUM_CMS_MAJOR=4 - # Get the default branch from GitHub API # We need to make an API call rather than just assume that the current branch is the default # because this workflow may be triggered by workflow_dispatch on any branch @@ -77,7 +90,7 @@ runs: rm __composer.json fi - BRANCHES=$(MINIMUM_CMS_MAJOR=$MINIMUM_CMS_MAJOR DEFAULT_BRANCH=$DEFAULT_BRANCH GITHUB_REPOSITORY=$GITHUB_REPOSITORY php ${{ github.action_path }}/branches.php) + BRANCHES=$(DEFAULT_BRANCH=$DEFAULT_BRANCH GITHUB_REPOSITORY=$GITHUB_REPOSITORY php ${{ github.action_path }}/branches.php) echo "BRANCHES is $BRANCHES" if [[ $BRANCHES =~ "^FAILURE \- (.+)$" ]]; then MESSAGE=${BASH_REMATCH[1]} diff --git a/branches.php b/branches.php index 7306d62..e1b3542 100644 --- a/branches.php +++ b/branches.php @@ -1,10 +1,16 @@ '3', -]; +/** + * The path to the composer.json file - note that the action explicitly downloads the composer.json + * file for the default branch of the repository and saves it to this path, rather than using + * the composer.json file in the "current" branch. + */ +const COMPOSER_JSON_PATH = '__composer.json'; function branches( string $defaultBranch, - string $minimumCmsMajor, string $githubRepository, - // The following params are purely for unit testing, for the actual github action it will read json files instead - string $composerJson = '', - string $branchesJson = '', - string $tagsJson = '' -) { - if (!is_numeric($defaultBranch)) { - throw new Exception('Default branch must be a number'); - } - if (!ctype_digit($minimumCmsMajor)) { - throw new Exception('Minimum CMS major must be an integer'); - } - - // work out default major - preg_match('#^([0-9]+)+\.?[0-9]*$#', $defaultBranch, $matches); - $defaultMajor = $matches[1]; - - // read __composer.json of the current (default) branch - if ($composerJson) { - $contents = $composerJson; - } elseif (file_exists('__composer.json')) { - $contents = file_get_contents('__composer.json'); - } else { - // respository such as silverstripe/eslint-config or silverstripe/gha-auto-tag - // make some fake json so that this branch is treated as though it's latest supported major version - $contents = json_encode([ - 'require' => [ - 'silverstripe/framework' => '^' . CURRENT_CMS_MAJOR, - ], - ], JSON_UNESCAPED_SLASHES); - } - - $json = json_decode($contents); - if (is_null($json)) { - $lastError = json_last_error(); - throw new Exception("Could not parse __composer.json - last error was $lastError"); - } - $defaultCmsMajor = ''; - $matchedOnBranchThreeLess = false; - $version = ''; - if ($githubRepository === 'silverstripe/developer-docs') { - $version = $defaultBranch; - } - if (!$version) { - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/framework'} ?? ''); - } - if (!$version) { - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/cms'} ?? ''); - } - if (!$version) { - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/mfa'} ?? ''); - } - if (!$version) { - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/assets'} ?? ''); - if ($version) { - $matchedOnBranchThreeLess = true; - } - } - if (!$version) { - $version = preg_replace('#[^0-9\.]#', '', $json->require->{'cwp/starter-theme'} ?? ''); - if ($version) { - $version += 1; - } - } - if (preg_match('#^([0-9]+)+\.?[0-9]*$#', $version, $matches)) { - $defaultCmsMajor = $matches[1]; - if ($matchedOnBranchThreeLess) { - $defaultCmsMajor += 3; +): array { + if (file_exists(COMPOSER_JSON_PATH)) { + $contents = file_get_contents(COMPOSER_JSON_PATH); + $composerJson = json_decode($contents); + if (is_null($composerJson)) { + $lastError = json_last_error(); + throw new Exception('Could not parse ' . COMPOSER_JSON_PATH . " - last error was $lastError"); } } else { - $phpVersion = $json->require->{'php'} ?? ''; - if (substr($phpVersion,0, 4) === '^7.4') { - $defaultCmsMajor = 4; - } elseif (substr($phpVersion,0, 4) === '^8.1') { - $defaultCmsMajor = 5; - } - } - if ($defaultCmsMajor === '') { - throw new Exception('Could not work out what the default CMS major version this module uses'); - } - // work out major diff e.g for silverstripe/admin for CMS 5 => 5 - 2 = 3 - $majorDiff = $defaultCmsMajor - $defaultMajor; - - $minorsWithStableTags = []; - $contents = $tagsJson ?: file_get_contents('__tags.json'); - foreach (json_decode($contents) as $row) { - $tag = $row->name; - if (!preg_match('#^([0-9]+)\.([0-9]+)\.([0-9]+)$#', $tag, $matches)) { - continue; - } - $major = $matches[1]; - $minor = $major. '.' . $matches[2]; - $minorsWithStableTags[$major][$minor] = true; - } - - $branches = []; - $contents = $branchesJson ?: file_get_contents('__branches.json'); - foreach (json_decode($contents) as $row) { - $branch = $row->name; - // filter out non-standard branches - if (!preg_match('#^([0-9]+)+\.?[0-9]*$#', $branch, $matches)) { - continue; - } - // filter out majors that are too old - $major = $matches[1]; - if (($major + $majorDiff) < $minimumCmsMajor) { - continue; - } - // suffix a temporary .999 minor version to major branches so that it's sorted correctly later - if (preg_match('#^[0-9]+$#', $branch)) { - $branch .= '.999'; - } - $branches[] = $branch; - } - - // sort so that newest is first - usort($branches, 'version_compare'); - $branches = array_reverse($branches); - - // remove the temporary .999 - array_walk($branches, function(&$branch) { - $branch = preg_replace('#\.999$#', '', $branch); - }); - - // remove all branches except: - // - the latest major branch in each release line - // - the latest minor branch with a stable tag in each release line - // - any minor branches without stable tags with a higher minor version than the latest minor with a stable tag - $foundMinorInMajor = []; - $foundMinorBranchWithStableTag = []; - foreach ($branches as $i => $branch) { - // only remove minor branches, leave major branches in - if (!preg_match('#^([0-9]+)\.[0-9]+$#', $branch, $matches)) { - continue; - } - $major = $matches[1]; - if (isset($foundMinorBranchWithStableTag[$major]) && isset($foundMinorInMajor[$major])) { - unset($branches[$i]); - continue; - } - // for developer-docs which has no tags, pretend that every branch has a tag - if (isset($minorsWithStableTags[$major][$branch]) || $githubRepository === 'silverstripe/developer-docs') { - $foundMinorBranchWithStableTag[$major] = true; - } - $foundMinorInMajor[$major] = true; - } - - // remove any branches less than or equal to DO_NOT_MERGE_UP_FROM_MAJOR - if (isset(DO_NOT_MERGE_UP_FROM_MAJOR[$githubRepository])) { - $doNotMergeUpFromMajor = DO_NOT_MERGE_UP_FROM_MAJOR[$githubRepository]; - $branches = array_filter($branches, function($branch) use ($doNotMergeUpFromMajor) { - return version_compare($branch, "$doNotMergeUpFromMajor.999999.999999", '>'); - }); + $composerJson = null; } - // reverse the array so that oldest is first - $branches = array_reverse($branches); + $repoMetaData = MetaData::getMetaDataForRepository($githubRepository); + $allRepoTags = array_map(fn($x) => $x->name, json_decode(file_get_contents('__tags.json'))); + $allRepoBranches = array_map(fn($x) => $x->name, json_decode(file_get_contents('__branches.json'))); + $branches = BranchLogic::getBranchesForMergeUp($githubRepository, $repoMetaData, $defaultBranch, $allRepoTags, $allRepoBranches, $composerJson); // max of 6 branches - also update action.yml if you need to increase this limit if (count($branches) > 6) { throw new Exception('More than 6 branches to merge up. Aborting.'); } - return $branches; } diff --git a/tests/BranchesTest.php b/tests/BranchesTest.php index 7c49a3e..f4fa8bb 100644 --- a/tests/BranchesTest.php +++ b/tests/BranchesTest.php @@ -10,26 +10,36 @@ class BranchesTest extends TestCase public function testBranches( array $expected, string $defaultBranch, - string $minimumCmsMajor, string $githubRepository, string $composerJson = '', - string $branchesJson = '', - string $tagsJson = '' + string $branchesJson = '[]', + string $tagsJson = '[]' ) { $expectException = $expected === ['__exception__']; if ($expectException) { $this->expectException(Exception::class); } - $actual = branches( - $defaultBranch, - $minimumCmsMajor, - $githubRepository, - $composerJson, - $branchesJson, - $tagsJson - ); - if (!$expectException) { - $this->assertSame($expected, $actual); + try { + if ($composerJson) { + file_put_contents('__composer.json', $composerJson); + } + file_put_contents('__branches.json', $branchesJson); + file_put_contents('__tags.json', $tagsJson); + $actual = branches( + $defaultBranch, + $githubRepository, + $branchesJson, + $tagsJson + ); + if (!$expectException) { + $this->assertSame($expected, $actual); + } + } finally { + if ($composerJson) { + unlink('__composer.json'); + } + unlink('__branches.json'); + unlink('__tags.json'); } } @@ -39,7 +49,6 @@ public function provideBranches() '5.1.0-beta1, CMS 6 branch detected on silverstripe/framework' => [ 'expected' => ['4.13', '4', '5.0', '5.1', '5', '6'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['4.13', '4', '5.1', '5'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['1.13', '2.0', '2.1', '2'], 'defaultBranch' => '2', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['1.13', '1', '2.1', '2.2', '2.3', '2'], 'defaultBranch' => '2', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['5.9', '5', '6.0', '6', '7'], 'defaultBranch' => '5', // this repo has a `5` branch for CMS 4 and a '6' branch for CMS 5 - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['4.13', '4', '5.0', '5'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'silverstripe/developer-docs', 'composerJson' => << '[]', + 'tagsJson' => '[ + {"name": "4.13.0"}, + {"name": "5.0.0"} + ]', ], 'More than 6 branches exception' => [ 'expected' => ['__exception__'], 'defaultBranch' => '5', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['3.2', '3', '4.0', '4'], 'defaultBranch' => '4', - 'minimumCmsMajor' => '4', 'githubRepository' => 'lorem/ipsum', 'composerJson' => << [ 'expected' => ['1.4', '1'], 'defaultBranch' => '1', - 'minimumCmsMajor' => '4', 'githubRepository' => 'silverstripe/gha-ci', 'composerJson' => '', 'branchesJson' => << [ + 'expected' => ['1.4', '1'], + 'defaultBranch' => '1', + 'githubRepository' => 'silverstripe/gha-generate-matrix', + 'composerJson' => << << << [ 'expected' => ['4.0', '4', '5'], 'defaultBranch' => '4', - 'minimumCmsMajor' => '4', 'githubRepository' => 'silverstripe/silverstripe-linkfield', 'composerJson' => << [ 'expected' => ['4.0', '4', '5'], 'defaultBranch' => '4', - 'minimumCmsMajor' => '4', 'githubRepository' => 'silverstripe/silverstripe-linkfield', 'composerJson' => << [ + 'expected' => ['4.13', '4', '5.1', '5'], + 'defaultBranch' => 'main', + 'githubRepository' => 'silverstripe/silverstripe-cms', + 'composerJson' => '', + 'branchesJson' => << << [ + 'expected' => ['4.13', '4', '5.1', '5'], + 'defaultBranch' => 'main', + 'githubRepository' => 'silverstripe/silverstripe-cms', + 'composerJson' => '', + 'branchesJson' => << << [ + 'expected' => ['__exception__'], + 'defaultBranch' => 'main', + 'githubRepository' => 'lorem/ipsum', + 'composerJson' => << << << [ + 'expected' => ['6.0', '6', '7.1', '7', '8'], + 'defaultBranch' => '7', + 'githubRepository' => 'tractorcow-farm/silverstripe-fluent', + 'composerJson' => '', + 'branchesJson' => << <<