This commit is contained in:
Chris Loftus 2026-02-25 11:43:06 +01:00 committed by GitHub
commit bfb3ea17aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 127 additions and 37 deletions

View file

@ -43,6 +43,7 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed'); $removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified'); $modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
$skip_deploy_commits = self::allCommitsSkipDeploy(data_get($payload, 'commits', []));
} }
if ($x_github_event === 'pull_request') { if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action'); $action = data_get($payload, 'action');
@ -102,31 +103,39 @@ class Github extends Controller
if ($application->isDeployable()) { if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || blank($application->watch_paths)) { if ($is_watch_path_triggered || blank($application->watch_paths)) {
$deployment_uuid = new Cuid2; if ($skip_deploy_commits ?? false) {
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'after', 'HEAD'),
is_webhook: true,
);
if ($result['status'] === 'queue_full') {
return response($result['message'], 429)->header('Retry-After', 60);
} elseif ($result['status'] === 'skipped') {
$return_payloads->push([ $return_payloads->push([
'application' => $application->name, 'application' => $application->name,
'status' => 'skipped', 'status' => 'skipped',
'message' => $result['message'], 'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
]); ]);
} else { } else {
$return_payloads->push([ $deployment_uuid = new Cuid2;
'application' => $application->name, $result = queue_application_deployment(
'status' => 'success', application: $application,
'message' => 'Deployment queued.', deployment_uuid: $deployment_uuid,
'application_uuid' => $application->uuid, force_rebuild: false,
'application_name' => $application->name, commit: data_get($payload, 'after', 'HEAD'),
'deployment_uuid' => $result['deployment_uuid'], is_webhook: true,
]); );
if ($result['status'] === 'queue_full') {
return response($result['message'], 429)->header('Retry-After', 60);
} elseif ($result['status'] === 'skipped') {
$return_payloads->push([
'application' => $application->name,
'status' => 'skipped',
'message' => $result['message'],
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Deployment queued.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $result['deployment_uuid'],
]);
}
} }
} else { } else {
$paths = str($application->watch_paths)->explode("\n"); $paths = str($application->watch_paths)->explode("\n");
@ -234,6 +243,7 @@ class Github extends Controller
$removed_files = data_get($payload, 'commits.*.removed'); $removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified'); $modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten(); $changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
$skip_deploy_commits = self::allCommitsSkipDeploy(data_get($payload, 'commits', []));
} }
if ($x_github_event === 'pull_request') { if ($x_github_event === 'pull_request') {
$action = data_get($payload, 'action'); $action = data_get($payload, 'action');
@ -285,24 +295,33 @@ class Github extends Controller
if ($application->isDeployable()) { if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files); $is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || blank($application->watch_paths)) { if ($is_watch_path_triggered || blank($application->watch_paths)) {
$deployment_uuid = new Cuid2; if ($skip_deploy_commits ?? false) {
$result = queue_application_deployment( $return_payloads->push([
application: $application, 'status' => 'skipped',
deployment_uuid: $deployment_uuid, 'message' => 'All commits contain [skip cd] or [skip ci]. Skipping deployment.',
commit: data_get($payload, 'after', 'HEAD'), 'application_uuid' => $application->uuid,
force_rebuild: false, 'application_name' => $application->name,
is_webhook: true, ]);
); } else {
if ($result['status'] === 'queue_full') { $deployment_uuid = new Cuid2;
return response($result['message'], 429)->header('Retry-After', 60); $result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
commit: data_get($payload, 'after', 'HEAD'),
force_rebuild: false,
is_webhook: true,
);
if ($result['status'] === 'queue_full') {
return response($result['message'], 429)->header('Retry-After', 60);
}
$return_payloads->push([
'status' => $result['status'],
'message' => $result['message'],
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $result['deployment_uuid'] ?? null,
]);
} }
$return_payloads->push([
'status' => $result['status'],
'message' => $result['message'],
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'deployment_uuid' => $result['deployment_uuid'] ?? null,
]);
} else { } else {
$paths = str($application->watch_paths)->explode("\n"); $paths = str($application->watch_paths)->explode("\n");
$return_payloads->push([ $return_payloads->push([
@ -418,4 +437,21 @@ class Github extends Controller
return handleError($e); return handleError($e);
} }
} }
/**
* Returns true if there is at least one commit and every commit message
* contains [skip cd] or [skip ci] (case-insensitive).
*
* @param array<int, array<string, mixed>> $commits
*/
public static function allCommitsSkipDeploy(array $commits): bool
{
$commitList = collect($commits);
return $commitList->isNotEmpty() && $commitList->every(function (array $commit): bool {
$message = strtolower((string) data_get($commit, 'message', ''));
return str_contains($message, '[skip cd]') || str_contains($message, '[skip ci]');
});
}
} }

View file

@ -0,0 +1,54 @@
<?php
use App\Http\Controllers\Webhook\Github;
describe('Github::allCommitsSkipDeploy', function () {
test('returns false when commits array is empty', function () {
expect(Github::allCommitsSkipDeploy([]))->toBeFalse();
});
test('returns true when all commits contain [skip ci]', function () {
$commits = [
['message' => 'Update docs [skip ci]'],
['message' => 'Fix typo [skip ci]'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeTrue();
});
test('returns true when all commits contain [skip cd]', function () {
$commits = [
['message' => 'Update README [skip cd]'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeTrue();
});
test('returns true when all commits contain either marker (case-insensitive)', function () {
$commits = [
['message' => 'Docs [SKIP CI]'],
['message' => 'Changelog [Skip Cd]'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeTrue();
});
test('returns false when at least one commit has no skip marker', function () {
$commits = [
['message' => 'Update docs [skip ci]'],
['message' => 'Actual feature change'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeFalse();
});
test('returns false when single commit has no skip marker', function () {
$commits = [
['message' => 'Deploy this please'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeFalse();
});
test('handles commit with missing message key', function () {
$commits = [
['id' => 'abc123'],
];
expect(Github::allCommitsSkipDeploy($commits))->toBeFalse();
});
});