Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Use supported modules repo for branch-based logic #35

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
*.json
!composer.json
.phpunit.result.cache
vendor/
composer.lock
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ This action has no inputs

## CMS major version detection

This action will identify the CMS major version which is determined by parsing the contents on the modules `composer.json` using logic in the `funcs.php` file. In cases where the major version remains indeterminable, the action will fail. This is most likely if the module is not intended as an addon for a website.
In cases where the major version is indeterminable, this action will fail. This is most likely if the module is not intended as an addon for a website.

To work around this, specify an arbitrary CMS major version by setting the required PHP version in `composer.json` to `^8.1` or any other minimum PHP version that aligns with a CMS major version.

## Preventing merge-ups from specific major versions of a repository

Update `DO_NOT_MERGE_UP_FROM_MAJOR` in `funcs.php`. For example to prevent merging up from the `3` major version of `silverstripe/silverstripe-linkfield`:
Update `MetaData::DO_NOT_MERGE_UP_FROM_MAJOR` in the [silverstripe/supported-modules](https://github.com/silverstripe/supported-modules) repo. For example to prevent merging up from the `3` major version of `silverstripe/silverstripe-linkfield`:

```php
const DO_NOT_MERGE_UP_FROM_MAJOR = [
public const DO_NOT_MERGE_UP_FROM_MAJOR = [
'silverstripe/silverstripe-linkfield' => '3',
];
```
28 changes: 21 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@ 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
shell: bash
run: |
cd ${{ github.action_path }}
composer install
cd -

- name: Determine if should merge-up
id: determine
Expand All @@ -17,10 +35,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
Expand Down Expand Up @@ -77,7 +91,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]}
Expand Down Expand Up @@ -123,7 +137,7 @@ runs:
fi
FILES=$(jq -r .files[].filename __compare.json)
rm __compare.json

# Don't allow merge-ups when there are changes in javascript dependency files
DEPENDENCY_FILES="package.json yarn.lock"
for DEPENDENCY_FILE in $DEPENDENCY_FILES; do
Expand Down Expand Up @@ -448,7 +462,7 @@ runs:
uses: silverstripe/gha-trigger-ci@v1
with:
branch: ${{ steps.git-merge-up.outputs.trigger_ci_branch_06 }}

- name: Trigger CI Exceeded
if: ${{ steps.git-merge-up.outputs.trigger_ci_exceeded != '' }}
shell: bash
Expand Down
10 changes: 8 additions & 2 deletions branches.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<?php

$autoloadPath = __DIR__ . '/vendor/autoload.php';
if (!file_exists($autoloadPath)) {
throw new RuntimeException('Run composer install before this script');
}

require_once $autoloadPath;

include 'funcs.php';

$defaultBranch = getenv('DEFAULT_BRANCH');
$minimumCmsMajor = getenv('MINIMUM_CMS_MAJOR');
$githubRepository = getenv('GITHUB_REPOSITORY');

$branches = branches($defaultBranch, $minimumCmsMajor, $githubRepository);
$branches = branches($defaultBranch, $githubRepository);
echo implode(' ', $branches);
8 changes: 8 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"require": {
"silverstripe/supported-modules": "dev-main"
},
"require-dev": {
"phpunit/phpunit": "^9.6"
}
}
182 changes: 20 additions & 162 deletions funcs.php
Original file line number Diff line number Diff line change
@@ -1,180 +1,38 @@
<?php

// This should always match default branch of silverstripe/framework
const CURRENT_CMS_MAJOR = 5;
use SilverStripe\SupportedModules\BranchLogic;
use SilverStripe\SupportedModules\MetaData;

// List of major branches to not merge up from
// Add repos in here where the repo was previously unsupported
// Note these are actual major branches, not CMS major versions
const DO_NOT_MERGE_UP_FROM_MAJOR = [
'silverstripe/silverstripe-linkfield' => '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;
}
Loading
Loading