Public Access
fef27eb5d1
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: mokocli CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: mokocli CI / Gate 4: Governance (push) Blocked by required conditions
Platform: mokocli CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: mokocli CI / CI Summary (push) Blocked by required conditions
Platform: mokocli CI / Gate 1: Code Quality (push) Failing after 1m17s
- cli/branch_protect_org.php: enforces push whitelist on main, dev, rc, beta, alpha across all org repos (--dry-run supported) - branch-protection-enforce.yml: runs weekly Monday 6am UTC + manual dispatch - Branch flow: feature/* -> dev -> rc -> main (no direct push to any)
150 lines
4.9 KiB
PHP
150 lines
4.9 KiB
PHP
<?php
|
|
/**
|
|
* @package MokoCLI
|
|
* @subpackage cli
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Enforce branch protection rules across all repos in the org.
|
|
*
|
|
* Usage:
|
|
* php cli/branch_protect_org.php --token TOKEN [--org MokoConsulting] [--dry-run]
|
|
*
|
|
* Branch flow: feature/* -> dev -> rc -> main
|
|
* main, dev, rc: push whitelist only (no direct push)
|
|
* alpha, beta: push whitelist only (pre-release)
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
$options = getopt('', ['token:', 'org:', 'api-base:', 'dry-run', 'help']);
|
|
|
|
if (isset($options['help']) || empty($options['token'])) {
|
|
echo "Usage: php cli/branch_protect_org.php --token TOKEN [--org ORG] [--api-base URL] [--dry-run]\n";
|
|
echo "\n";
|
|
echo "Options:\n";
|
|
echo " --token Gitea API token (required)\n";
|
|
echo " --org Organization name (default: MokoConsulting)\n";
|
|
echo " --api-base API base URL (default: https://git.mokoconsulting.tech/api/v1)\n";
|
|
echo " --dry-run Show what would be changed without making changes\n";
|
|
exit(0);
|
|
}
|
|
|
|
$token = $options['token'];
|
|
$org = $options['org'] ?? 'MokoConsulting';
|
|
$apiBase = rtrim($options['api-base'] ?? 'https://git.mokoconsulting.tech/api/v1', '/');
|
|
$dryRun = isset($options['dry-run']);
|
|
|
|
// Protected branches and their rules
|
|
$branchRules = [
|
|
'main' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
|
'dev' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
|
'rc' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
|
'beta' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
|
'alpha' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']],
|
|
];
|
|
|
|
function apiRequest(string $method, string $url, string $token, ?array $body = null): array
|
|
{
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_CUSTOMREQUEST => $method,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: token ' . $token,
|
|
'Content-Type: application/json',
|
|
'Accept: application/json',
|
|
],
|
|
CURLOPT_TIMEOUT => 30,
|
|
]);
|
|
|
|
if ($body !== null) {
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
|
|
}
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
return [
|
|
'status' => $httpCode,
|
|
'data' => json_decode($response, true) ?: [],
|
|
];
|
|
}
|
|
|
|
// 1. List all org repos
|
|
echo "Fetching repos for {$org}...\n";
|
|
$page = 1;
|
|
$repos = [];
|
|
|
|
do {
|
|
$result = apiRequest('GET', "{$apiBase}/orgs/{$org}/repos?limit=50&page={$page}", $token);
|
|
$batch = $result['data'];
|
|
$repos = array_merge($repos, $batch);
|
|
$page++;
|
|
} while (count($batch) === 50);
|
|
|
|
echo sprintf("Found %d repos\n\n", count($repos));
|
|
|
|
$summary = ['protected' => 0, 'added' => 0, 'skipped' => 0, 'errors' => 0];
|
|
|
|
foreach ($repos as $repo) {
|
|
$repoName = $repo['name'];
|
|
|
|
if ($repo['archived'] ?? false) {
|
|
continue;
|
|
}
|
|
|
|
// Get existing protections
|
|
$existing = apiRequest('GET', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token);
|
|
$existingNames = array_map(fn($p) => $p['branch_name'] ?? '', $existing['data'] ?: []);
|
|
|
|
$added = [];
|
|
$skipped = [];
|
|
|
|
foreach ($branchRules as $branch => $rules) {
|
|
if (in_array($branch, $existingNames, true)) {
|
|
$skipped[] = $branch;
|
|
$summary['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
if ($dryRun) {
|
|
$added[] = $branch;
|
|
$summary['added']++;
|
|
continue;
|
|
}
|
|
|
|
$body = array_merge($rules, ['branch_name' => $branch]);
|
|
$result = apiRequest('POST', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token, $body);
|
|
|
|
if ($result['status'] >= 200 && $result['status'] < 300) {
|
|
$added[] = $branch;
|
|
$summary['added']++;
|
|
} elseif ($result['status'] === 422) {
|
|
$skipped[] = $branch;
|
|
$summary['skipped']++;
|
|
} else {
|
|
$added[] = "{$branch}(ERR:{$result['status']})";
|
|
$summary['errors']++;
|
|
}
|
|
}
|
|
|
|
$summary['protected']++;
|
|
|
|
if (!empty($added)) {
|
|
$prefix = $dryRun ? '[DRY-RUN] ' : '';
|
|
echo sprintf(" %s%-35s added: %s\n", $prefix, $repoName, implode(', ', $added));
|
|
}
|
|
}
|
|
|
|
echo "\n";
|
|
echo sprintf("Summary: %d repos, %d rules added, %d already existed, %d errors\n",
|
|
$summary['protected'], $summary['added'], $summary['skipped'], $summary['errors']);
|
|
|
|
if ($dryRun) {
|
|
echo "\n(Dry run - no changes made)\n";
|
|
}
|