From f4f4274ceea20754ecf4b314b63375946b0b976e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 8 Jul 2026 13:31:23 +0200 Subject: [PATCH] fix(github): skip PR previews from head commit flags --- app/Jobs/ProcessGithubPullRequestWebhook.php | 14 ++++-- bootstrap/helpers/github.php | 22 ++++++++ .../ProcessGithubPullRequestWebhookTest.php | 50 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/app/Jobs/ProcessGithubPullRequestWebhook.php b/app/Jobs/ProcessGithubPullRequestWebhook.php index 61fc3d4ee..666888a57 100644 --- a/app/Jobs/ProcessGithubPullRequestWebhook.php +++ b/app/Jobs/ProcessGithubPullRequestWebhook.php @@ -96,7 +96,16 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp return; } - if (self::shouldSkipDeployAny([$this->pullRequestTitle])) { + $repository_parts = explode('/', $this->fullName); + $owner = $repository_parts[0] ?? ''; + $repo = $repository_parts[1] ?? ''; + $headCommitMessage = null; + + if ($this->action === 'synchronize') { + $headCommitMessage = getGithubCommitMessage($githubApp, $owner, $repo, $this->commitSha); + } + + if (self::shouldSkipDeployAny([$this->pullRequestTitle, $headCommitMessage])) { return; } @@ -120,9 +129,6 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp // Get changed files for watch path filtering $changed_files = collect(); - $repository_parts = explode('/', $this->fullName); - $owner = $repository_parts[0] ?? ''; - $repo = $repository_parts[1] ?? ''; if ($this->action === 'synchronize' && $this->beforeSha && $this->afterSha) { // For synchronize events, get files changed between before and after commits diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 052ce6d9f..465500b06 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -414,6 +414,28 @@ function getGithubCommitRangeFiles(?GithubApp $source, string $owner, string $re } } +function getGithubCommitMessage(?GithubApp $source, string $owner, string $repo, string $commitSha): ?string +{ + try { + if (! $source) { + return null; + } + + if (blank($owner) || blank($repo) || blank($commitSha) || $commitSha === 'HEAD') { + return null; + } + + $endpoint = "/repos/{$owner}/{$repo}/commits/{$commitSha}"; + $response = githubApi($source, $endpoint, 'get', null, false); + + $message = data_get($response, 'data.commit.message'); + + return is_string($message) ? $message : null; + } catch (Exception $e) { + return null; + } +} + function getGithubPullRequestFiles(?GithubApp $source, string $owner, string $repo, int $pullRequestId): array { try { diff --git a/tests/Feature/ProcessGithubPullRequestWebhookTest.php b/tests/Feature/ProcessGithubPullRequestWebhookTest.php index ce8e8c5ba..557e42ce8 100644 --- a/tests/Feature/ProcessGithubPullRequestWebhookTest.php +++ b/tests/Feature/ProcessGithubPullRequestWebhookTest.php @@ -3,14 +3,19 @@ use App\Actions\Application\CleanupPreviewDeployment; use App\Jobs\ProcessGithubPullRequestWebhook; use App\Models\Application; +use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationPreview; use App\Models\Environment; +use App\Models\GithubApp; use App\Models\InstanceSettings; use App\Models\Project; use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\Team; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Client\Request; +use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Queue; uses(RefreshDatabase::class); @@ -58,3 +63,48 @@ protected function dispatchPullRequestClosedUpdate(Application $application, App $job->handle(); }); + +it('skips a synchronized GitHub pull request preview when the head commit message contains skip ci', function () { + Queue::fake(); + + $this->application->settings->update([ + 'is_preview_deployments_enabled' => true, + ]); + + $githubApp = GithubApp::create([ + 'name' => 'Public GitHub', + 'api_url' => 'https://api.github.com', + 'html_url' => 'https://github.com', + 'is_public' => true, + 'team_id' => $this->team->id, + ]); + + Http::fake([ + 'https://api.github.com/repos/example/repo/commits/after-sha' => Http::response([ + 'commit' => [ + 'message' => 'docs: fix typo [skip ci]', + ], + ]), + ]); + + $job = new ProcessGithubPullRequestWebhook( + applicationId: $this->application->id, + githubAppId: $githubApp->id, + action: 'synchronize', + pullRequestId: 42, + pullRequestHtmlUrl: 'https://github.com/example/repo/pull/42', + pullRequestTitle: 'Add feature', + beforeSha: 'before-sha', + afterSha: 'after-sha', + commitSha: 'after-sha', + authorAssociation: 'OWNER', + fullName: 'example/repo', + ); + + $job->handle(); + + expect(ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', 42)->exists())->toBeFalse() + ->and(ApplicationDeploymentQueue::where('application_id', $this->application->id)->where('pull_request_id', 42)->exists())->toBeFalse(); + + Http::assertSent(fn (Request $request): bool => $request->url() === 'https://api.github.com/repos/example/repo/commits/after-sha'); +});