chore: merge main into release/v4.5 (#12192)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Furox <177975472+Furox-Art@users.noreply.github.com>
Co-authored-by: johandenhollander <johan.den.hollander@finalist.nl>
Co-authored-by: João Cruz <161638790+SrJooJ@users.noreply.github.com>
Co-authored-by: Saim Hashmi <hashmisaim037@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
github-actions[bot]
2026-10-18 23:43:20 +02:00
committed by GitHub
co-authored by Andras Bacsai Claude Opus 5.5 Furox johandenhollander João Cruz Saim Hashmi dependabot[bot]
parent 5dddcfd276
commit e3d4411ff2
246 changed files with 32148 additions and 1316 deletions
+46 -12
View File
@@ -72,7 +72,6 @@ class Github extends Controller
'before_sha' => $before_sha,
'after_sha' => $after_sha,
'commit_sha' => $commit_sha,
'author_association' => $author_association,
'is_fork_pull_request' => $is_fork_pull_request,
] = $this->readPullRequestPayload($payload);
$full_name = data_get($payload, 'repository.full_name');
@@ -241,7 +240,6 @@ class Github extends Controller
beforeSha: $before_sha,
afterSha: $after_sha,
commitSha: $commit_sha,
authorAssociation: $author_association,
fullName: $full_name,
isForkPullRequest: $is_fork_pull_request,
);
@@ -402,7 +400,12 @@ class Github extends Controller
// Just pong
return response('pong');
}
$github_app = GithubApp::where('app_id', $x_github_hook_installation_target_id)->first();
// One GitHub App can be installed on several accounts, each added as its own source.
$installation_id = data_get($payload, 'installation.id');
$github_app = is_numeric($installation_id)
? GithubApp::where('app_id', $x_github_hook_installation_target_id)->where('installation_id', (int) $installation_id)->first()
: null;
$github_app ??= GithubApp::where('app_id', $x_github_hook_installation_target_id)->first();
if (is_null($github_app)) {
return response('Nothing to do. No GitHub App found.');
}
@@ -462,7 +465,6 @@ class Github extends Controller
'before_sha' => $before_sha,
'after_sha' => $after_sha,
'commit_sha' => $commit_sha,
'author_association' => $author_association,
'is_fork_pull_request' => $is_fork_pull_request,
] = $this->readPullRequestPayload($payload);
$full_name = $this->manualWebhookRepositoryFullName(data_get($payload, 'repository.full_name'));
@@ -487,6 +489,9 @@ class Github extends Controller
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$branch'.");
}
if ($applications->contains(fn (Application $application): bool => filled($application->watch_paths))) {
$changed_files = $this->githubPushComparedFiles($github_app, $payload) ?? $changed_files;
}
}
if ($x_github_event === 'pull_request') {
if ($action !== 'closed') {
@@ -601,7 +606,6 @@ class Github extends Controller
beforeSha: $before_sha,
afterSha: $after_sha,
commitSha: $commit_sha,
authorAssociation: $author_association,
fullName: $full_name,
isForkPullRequest: $is_fork_pull_request,
);
@@ -623,10 +627,39 @@ class Github extends Controller
}
}
/**
* Files that the push changes between its before and after commits, from
* the GitHub compare API.
*
* The file lists of the pushed commits are not the net change: a merge
* commit names the files that it changes relative to its first parent,
* also when the push does not change them. Returns null for a new branch,
* a missing repository name or an API failure; the caller then uses the
* file lists of the pushed commits.
*
* @return Collection<int, string>|null
*
* @throws InvalidWebhookPayloadException When the before or after value is not a commit SHA.
*/
private function githubPushComparedFiles(GithubApp $githubApp, mixed $payload): ?Collection
{
$before = $this->webhookCommitSha($payload, 'before');
$after = $this->webhookCommitSha($payload, 'after');
$fullName = $this->manualWebhookRepositoryFullName(data_get($payload, 'repository.full_name'));
if ($before === null || $after === null || $fullName === null || preg_match('/\A0+\z/', $before) === 1) {
return null;
}
[$owner, $repo] = explode('/', $fullName, 2);
$files = getGithubCommitRangeFiles($githubApp, $owner, $repo, $before, $after);
return $files === null ? null : collect($files);
}
/**
* Read and validate the pull_request payload fields that the handlers use.
*
* @return array{action: string, pull_request_id: int, pull_request_html_url: string, pull_request_title: ?string, branch: ?string, base_branch: ?string, before_sha: ?string, after_sha: ?string, commit_sha: string, author_association: ?string, is_fork_pull_request: bool}
* @return array{action: string, pull_request_id: int, pull_request_html_url: string, pull_request_title: ?string, branch: ?string, base_branch: ?string, before_sha: ?string, after_sha: ?string, commit_sha: string, is_fork_pull_request: bool}
*
* @throws InvalidWebhookPayloadException When a field has a wrong type or format.
*/
@@ -644,7 +677,6 @@ class Github extends Controller
'before_sha' => $this->webhookCommitSha($payload, 'before'),
'after_sha' => $this->webhookCommitSha($payload, 'after') ?? $head_sha,
'commit_sha' => $head_sha ?? 'HEAD',
'author_association' => $this->webhookPayloadString($payload, 'pull_request.author_association'),
'is_fork_pull_request' => $this->isForkPullRequest($payload),
];
}
@@ -652,13 +684,15 @@ class Github extends Controller
/**
* Determine whether a pull_request webhook payload originates from a fork.
*
* GitHub's `author_association` is not a reliable trust signal (it grants
* CONTRIBUTOR to anyone who has merely opened an issue/PR before), so fork
* detection is gated on whether the PR crosses repository boundaries.
* A same-repository PR branch can only be pushed by users with write access,
* so this is the trust signal for preview deployments. GitHub's
* `author_association` is not reliable for this: users with access through a
* team or a private organization membership arrive as CONTRIBUTOR or NONE.
*
* The repository id comparison is the canonical signal; the `head.repo.fork`
* flag and a case-insensitive full_name comparison are fallbacks for payloads
* where the ids are unavailable (e.g. a deleted head repository).
* where the ids are unavailable. A PR whose head repository is unknown (e.g.
* a deleted fork) is treated as a fork.
*
* @throws InvalidWebhookPayloadException When a repository id is not a positive integer.
*/
@@ -682,7 +716,7 @@ class Github extends Controller
return Str::lower($headRepoFullName) !== Str::lower($baseRepoFullName);
}
return false;
return true;
}
public function redirect(Request $request)