Skip to content

Commit

Permalink
NEW Add merge-ups
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Aug 14, 2023
1 parent 98fe0ab commit 8ca3e2d
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 73 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
- name: Install PHP
uses: shivammathur/setup-php@1a18b2267f80291a81ca1d33e7c851fe09e7dfc4 # v2.22.0
with:
php-version: 7.4

- name: Install PHPUnit
run: wget https://phar.phpunit.de/phpunit-9.5.phar
php-version: 8.1
- name: Composer install
run: composer install --prefer-dist --no-progress --no-suggest --ansi --no-interaction --no-scripts --no-plugins --optimize-autoloader

- name: PHPUnit
run: php phpunit-9.5.phar --verbose --colors=always
run: vendor/bin/phpunit --verbose --colors=always
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ MS_GITHUB_TOKEN=abc123 php run.php update --cms-major=5 --branch=next-minor --dr
| --dry-run | Do not push to github or create pull-requests |
| --account | GitHub account to use for creating pull-requests (default: creative-commoners) |
| --no-delete | Do not delete _data and _modules directories before running |
| --update-prs | Update existing open PRs instead of creating new PRs |

## GitHub API secondary rate limit

Expand Down
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"require": {
"php": ">=7.4",
"php": ">=8.1",
"symfony/console": "^6.3",
"symfony/process": "^6.3"
"symfony/process": "^6.3",
"panlatent/cron-expression-descriptor": "^1"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
"phpunit/phpunit": "^9.6"
}
}
39 changes: 35 additions & 4 deletions funcs_scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function delete_file_if_exists($filename)
}

/**
* Rename a file to the root of the module being processed if it exists
* Rename a file relative to the root of the module being processed if it exists
*
* Example usage:
* rename_file_if_exists('oldfilename.md', 'newfilename.md')
Expand All @@ -108,14 +108,30 @@ function rename_file_if_exists($oldFilename, $newFilename)
function module_is_recipe()
{
global $MODULE_DIR;
if (strpos('/recipe-', $MODULE_DIR) !== false
|| strpos('/silverstripe-installer', $MODULE_DIR) !== false
if (strpos($MODULE_DIR, '/recipe-') !== false
|| strpos($MODULE_DIR, '/silverstripe-installer') !== false
) {
return true;
}
return false;
}

/**
* Determine if the module being processed is something installed on a website e.g. silverstripe-admin, not gha-*
*
* Example usage:
* is_module()
*/
function is_module()
{
global $MODULE_DIR;
return strpos($MODULE_DIR, '/gha-') === false
&& strpos($MODULE_DIR, '/developer-docs') === false
&& strpos($MODULE_DIR, '/vendor-plugin') === false
&& strpos($MODULE_DIR, '/eslint-config') === false
&& strpos($MODULE_DIR, '/webpack-config') === false;
}

/**
* Determine if the module being processed is one of the modules in a list
*
Expand All @@ -132,7 +148,7 @@ function module_is_one_of($repos)
if (!is_string($repo)) {
error('repo is not a string');
}
if (strpos("/$repo", $MODULE_DIR) !== false) {
if (strpos($MODULE_DIR, "/$repo") !== false) {
return true;
}
}
Expand Down Expand Up @@ -192,3 +208,18 @@ function human_cron(string $cron): string
$str = preg_replace('# (AM|PM),#', ' $1 UTC,', $str);
return $str;
}

/**
* Creates a predicatable random int between 0 and $max based on the module name to be used with the % mod operator.
* $offset variable will offset both the min (0) and $max. e.g. $offset of 1 with a max of 27 will return an int
* between 1 and 28
* Note that this will return the exact same value every time it is called for a given module.
*/
function predictable_random_int($max, $offset = 0): int
{
global $MODULE_DIR;
$chars = str_split($MODULE_DIR);
$codes = array_map(fn($c) => ord($c), $chars);
mt_srand(array_sum($codes));
return mt_rand(0, $max) + $offset;
}
77 changes: 72 additions & 5 deletions funcs_utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function write_file($path, $contents)
}
$dirname = dirname($path);
if (!file_exists($dirname)) {
error("Directory $dirname does not exist");
mkdir($dirname, 0775, true);
}
$contents = trim($contents) . "\n";
file_put_contents($path, $contents);
Expand Down Expand Up @@ -67,12 +67,43 @@ function supported_modules($cmsMajor)
'account' => explode('/', $ghrepo)[0],
'repo' => explode('/', $ghrepo)[1],
'cloneUrl' => "git@github.com:$ghrepo.git",
'branch' => max($module['branches'] ?: [-1])
];
}
return $modules;
}

/**
* Hardcoded list of additional repositories to standardise (e.g. silverstripe/gha-*)
*
* Repositories in this list should only have a single supported major version
* This will only be included if the $cmsMajor is the CURRENT_CMS_MAJOR
*/
function extra_repositories()
{
$modules = [];
// iterating to page 7 should be enough to get all the repos well into the future
for ($i = 0; $i < 7; $i++) {
$json = github_api("https://api.github.com/orgs/silverstripe/repos?per_page=100&page=$i");
foreach ($json as $repo) {
if ($repo['archived']) {
continue;
}
$ghrepo = $repo['full_name'];
// exclude non gha-* repos
if (strpos($ghrepo, '/gha-') === false) {
continue;
}
$modules[] = [
'ghrepo' => $ghrepo,
'account' => explode('/', $ghrepo)[0],
'repo' => explode('/', $ghrepo)[1],
'cloneUrl' => "git@github.com:$ghrepo.git",
];
}
}
return $modules;
}

/**
* Returns a list of all scripts files to run against a particular cms major version
*/
Expand Down Expand Up @@ -169,6 +200,8 @@ function github_token()
*/
function github_api($url, $data = [])
{
// silverstripe-themes has a kind of weird redirect only for api requests
$url = str_replace('/silverstripe-themes/silverstripe-simple', '/silverstripe/silverstripe-simple', $url);
$token = github_token();
$jsonStr = empty($data) ? '' : json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ch = curl_init($url);
Expand Down Expand Up @@ -265,12 +298,38 @@ function branch_to_checkout($branches, $currentBranch, $currentBranchCmsMajor, $
return (string) $branchToCheckout;
}

/**
* Uses composer.json to workout the current branch cms major version
*
* If composer.json does not exist then it's assumed to be CURRENT_CMS_MAJOR
*/
function current_branch_cms_major(
// this param is only used for unit testing
string $composerJson = ''
) {
// read __composer.json of the current branch
$contents = $composerJson ?: read_file('composer.json');
global $MODULE_DIR;

if ($composerJson) {
$contents = $composerJson;
} elseif (check_file_exists('composer.json')) {
$contents = read_file('composer.json');
} else {
return CURRENT_CMS_MAJOR;
}

// special logic for developer-docs
if (strpos($MODULE_DIR, '/developer-docs') !== false) {
$currentBranch = cmd('git rev-parse --abbrev-ref HEAD', $MODULE_DIR);
if (!preg_match('#^(pulls/)?([0-9]+)(\.[0-9]+)?(/|$)#', $currentBranch, $matches)) {
error("Could work out current major for developer-docs from branch $currentBranch");
}
return $matches[2];
}

// special logic for silverstripe-themes/silverstripe-simple
if (strpos($MODULE_DIR, '/silverstripe-simple') !== false) {
return CURRENT_CMS_MAJOR;
}

$json = json_decode($contents);
if (is_null($json)) {
Expand All @@ -287,7 +346,15 @@ function current_branch_cms_major(
}
if (!$version) {
$version = preg_replace('#[^0-9\.]#', '', $json->require->{'silverstripe/assets'} ?? '');
$matchedOnBranchThreeLess = true;
if ($version) {
$matchedOnBranchThreeLess = true;
}
}
if (!$version) {
$version = preg_replace('#[^0-9\.]#', '', $json->require->{'cwp/starter-theme'} ?? '');
if ($version) {
$version += 1;
}
}
$cmsMajor = '';
if (preg_match('#^([0-9]+)+\.?[0-9]*$#', $version, $matches)) {
Expand Down
14 changes: 13 additions & 1 deletion run.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,17 @@
InputOption::VALUE_NONE,
'Do not delete _data and _modules directories before running'
)
->addOption(
'update-prs',
null,
InputOption::VALUE_NONE,
'Checkout out and update the latest open PR instead of creating a new one'
)
->setCode($updateCommand);
$app->run();

try {
$app->run();
} catch (Error|Exception $e) {
// Make sure we output and information about PRs which were raised before killing the process.
error("file: {$e->getFile()}\nline: {$e->getLine()}\nmessage: {$e->getMessage()}");
}
4 changes: 3 additions & 1 deletion scripts/cms-any/editorconfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@
insert_final_newline = false
EOT;

write_file_if_not_exist('.editorconfig', $contents);
if (is_module()) {
write_file_if_not_exist('.editorconfig', $contents);
}
34 changes: 34 additions & 0 deletions scripts/cms5/keepalive.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

// run on a day of the month up to the 28th
$runOnDay = predictable_random_int(27, 1);
// run at a random hour of the day
$runOnHour = predictable_random_int(23);
// run at a random minute of the hour rounded to 5 minutes
$runOnMinute = predictable_random_int(11) * 5;

$cron = "$runOnMinute $runOnHour $runOnDay * *";
$humanCron = human_cron($cron);
$account = module_account();

$content = <<<EOT
name: Keepalive
on:
# $humanCron
schedule:
- cron: '$cron'
workflow_dispatch:
jobs:
keepalive:
name: Keepalive
# Only run cron on the $account account
if: (github.event_name == 'schedule' && github.repository_owner == '$account') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1
EOT;

write_file_even_if_exists('.github/workflows/keepalive.yml', $content);
59 changes: 59 additions & 0 deletions scripts/cms5/merge-ups.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

// run on a random day of the week
$runOnDay = predictable_random_int(6);
// run at a random hour of the day
$runOnHour = predictable_random_int(23);
// run at a random minute of the hour rounded to 5 minutes
$runOnMinute = predictable_random_int(11) * 5;

// If there's a CI workflow, offset mergeups from the CI run by 3 days
if (check_file_exists('.github/workflows/dispatch-ci.yml')) {
$ci = read_file('.github/workflows/dispatch-ci.yml');
preg_match("#- cron: '(.+?) (.+?) (.+?) (.+?) (.+?)'#", $ci, $matches);
[$_, $minute, $hour, $day, $month, $dayOfWeek] = $matches;
if ($dayOfWeek !== '*') {
$days = explode(',', $dayOfWeek);
$runOnDay = ($days[count($days) - 1] + 3) % 7;
}
if ($hour !== '*') {
$hours = explode(',', $hour);
$runOnHour = $hours[0];
}
if ($minute !== '*') {
$runOnMinute = $minute;
}
}

$cron = "$runOnMinute $runOnHour * * $runOnDay";
$humanCron = human_cron($cron);
$account = module_account();

$content = <<<EOT
name: Merge-up
on:
# $humanCron
schedule:
- cron: '$cron'
workflow_dispatch:
jobs:
merge-up:
name: Merge-up
# Only run cron on the $account account
if: (github.event_name == 'schedule' && github.repository_owner == '$account') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Merge-up
uses: silverstripe/gha-merge-up@v1
EOT;

// rename any existing misnamed merge-ups.yml to merge-up.yml
if (check_file_exists('.github/workflows/merge-ups.yml')) {
rename_file_if_exists('.github/workflows/merge-ups.yml', '.github/workflows/merge-up.yml');
}

if (!module_is_recipe()) {
write_file_even_if_exists('.github/workflows/merge-up.yml', $content);
}
Loading

0 comments on commit 8ca3e2d

Please sign in to comment.