Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 36 additions & 35 deletions app_dart/lib/src/request_handlers/github/webhook_subscription.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1105,50 +1105,21 @@ The "Merge" button is also unlocked. To bypass presubmits as well as the tree st
) ??
false;
if (hasEmergencyLabel) {
// The merge guard can be unlocked without approval checks because:
// The merge guard and Flutter Presubmits check can be unlocked without approval checks because:
//
// * For manual merges the GitHub repo settings already require minimum
// approvals before the PR can be submitted.
// * For `autosubmit` label Cocoon has the [Approval] validation that
// checks approvasl before attempting to merge the PR.
await _unlockMergeQueueGuardForEmergency();
// checks approvals before attempting to merge the PR.
await _unlockCheckrunsForEmergency();
} else {
logInfo('no emergency label; moving on.');
}
}

Future<void> _unlockMergeQueueGuardForEmergency() async {
logInfo(
'attempting to unlock the ${Config.kMergeQueueLockName} for emergency',
);

final guard = (await githubService.getCheckRunsFiltered(
slug: slug,
ref: pullRequest.head!.sha!,
checkName: Config.kMergeQueueLockName,
)).singleOrNull;

if (guard == null) {
logSevere(
'failed to process the emergency label. "${Config.kMergeQueueLockName}" check run is missing.',
);
return;
}

await githubService.updateCheckRun(
slug: slug,
checkRun: guard,
status: CheckRunStatus.completed,
conclusion: CheckRunConclusion.success,
output: const CheckRunOutput(
title: Config.kMergeQueueLockName,
summary: 'Emergency label applied.',
),
);

logInfo(
'unlocked "${Config.kMergeQueueLockName}", allowing it to land as an emergency.',
);
Future<void> _unlockCheckrunsForEmergency() async {
await _unlockCheckrun(Config.kMergeQueueLockName);
await _unlockCheckrun(Config.kFlutterPresubmitsName);

// Let the developer know what is happening with the MQ when this label is found the first time.
try {
Expand All @@ -1171,4 +1142,34 @@ The "Merge" button is also unlocked. To bypass presubmits as well as the tree st
);
}
}

Future<void> _unlockCheckrun(String checkName) async {
logInfo('attempting to unlock the $checkName for emergency');

final guard = (await githubService.getCheckRunsFiltered(
slug: slug,
ref: pullRequest.head!.sha!,
checkName: checkName,
)).singleOrNull;

if (guard == null) {
logSevere(
'failed to process the emergency label. "$checkName" check run is missing.',
);
return;
}

await githubService.updateCheckRun(
slug: slug,
checkRun: guard,
status: CheckRunStatus.completed,
conclusion: CheckRunConclusion.success,
output: CheckRunOutput(
title: checkName,
summary: 'Emergency label applied.',
),
);

logInfo('unlocked "$checkName", allowing it to land as an emergency.');
}
}
160 changes: 124 additions & 36 deletions app_dart/test/request_handlers/github/webhook_subscription_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3010,15 +3010,17 @@ void foo() {
});

group('PullRequestLabelProcessor.processLabels', () {
test('applies emergency label on approved PRs', () async {
final pullRequest = generatePullRequest(
number: 123,
headSha: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
labels: [IssueLabel(name: 'emergency')],
);
test(
'applies emergency label on approved PRs (Merge Queue Guard only)',
() async {
final pullRequest = generatePullRequest(
number: 123,
headSha: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
labels: [IssueLabel(name: 'emergency')],
);

githubService.checkRunsMock = '''{
"total_count": 2,
githubService.checkRunsMock = '''{
"total_count": 1,
"check_runs": [
{
"id": 2,
Expand All @@ -3035,35 +3037,46 @@ void foo() {
]
}''';

final pullRequestLabelProcessor = PullRequestLabelProcessor(
config: config,
githubService: githubService,
pullRequest: pullRequest,
);
final pullRequestLabelProcessor = PullRequestLabelProcessor(
config: config,
githubService: githubService,
pullRequest: pullRequest,
);

await pullRequestLabelProcessor.processLabels();
await pullRequestLabelProcessor.processLabels();

expect(
log,
bufferedLoggerOf(
containsAll([
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): attempting to unlock the Merge Queue Guard for emergency',
expect(
log,
bufferedLoggerOf(
containsAll([
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): attempting to unlock the Merge Queue Guard for emergency',
),
),
),
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): unlocked "Merge Queue Guard", allowing it to land as an emergency.',
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): unlocked "Merge Queue Guard", allowing it to land as an emergency.',
),
),
),
]),
),
);
});
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): attempting to unlock the Flutter Presubmits for emergency',
),
),
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): failed to process the emergency label. "Flutter Presubmits" check run is missing.',
),
),
]),
),
);
},
);

test(
'logs and gracefully skips emergency label on missing Merge Queue Guard',
'applies emergency label on approved PRs (Flutter Presubmits only)',
() async {
final pullRequest = generatePullRequest(
number: 123,
Expand All @@ -3072,16 +3085,16 @@ void foo() {
);

githubService.checkRunsMock = '''{
"total_count": 2,
"total_count": 1,
"check_runs": [
{
"id": 2,
"id": 3,
"head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
"external_id": "",
"details_url": "https://example.com",
"status": "in_progress",
"started_at": "2018-05-04T01:14:52Z",
"name": "Not A Guard For Sure",
"name": "Flutter Presubmits",
"check_suite": {
"id": 5
}
Expand Down Expand Up @@ -3111,6 +3124,81 @@ void foo() {
'PullRequestLabelProcessor(flutter/flutter/pull/123): failed to process the emergency label. "Merge Queue Guard" check run is missing.',
),
),
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): attempting to unlock the Flutter Presubmits for emergency',
),
),
logThat(
message: equals(
'PullRequestLabelProcessor(flutter/flutter/pull/123): unlocked "Flutter Presubmits", allowing it to land as an emergency.',
),
),
]),
),
);
},
);

test(
'logs and gracefully skips emergency label on missing checkruns',
() async {
final pullRequest = generatePullRequest(
number: 123,
headSha: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
labels: [IssueLabel(name: 'emergency')],
);

githubService.checkRunsMock = '''{
"total_count": 1,
"check_runs": [
{
"id": 2,
"head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
"external_id": "",
"details_url": "https://example.com",
"status": "in_progress",
"started_at": "2018-05-04T01:14:52Z",
"name": "Not A Guard For Sure",
"check_suite": {
"id": 5
}
}
]
}''';

final pullRequestLabelProcessor = PullRequestLabelProcessor(
config: config,
githubService: githubService,
pullRequest: pullRequest,
);

await pullRequestLabelProcessor.processLabels();

expect(
log,
bufferedLoggerOf(
containsAll([
logThat(
message: contains(
'attempting to unlock the Merge Queue Guard for emergency',
),
),
logThat(
message: contains(
'failed to process the emergency label. "Merge Queue Guard" check run is missing.',
),
),
logThat(
message: contains(
'attempting to unlock the Flutter Presubmits for emergency',
),
),
logThat(
message: contains(
'failed to process the emergency label. "Flutter Presubmits" check run is missing.',
),
),
]),
),
);
Expand Down Expand Up @@ -3153,7 +3241,7 @@ void foo() {
);
githubService.createdComments.clear();
githubService.checkRunsMock = '''{
"total_count": 2,
"total_count": 1,
"check_runs": [
{
"id": 2,
Expand Down Expand Up @@ -3197,7 +3285,7 @@ void foo() {
);
githubService.createdComments.clear();
githubService.checkRunsMock = '''{
"total_count": 2,
"total_count": 1,
"check_runs": [
{
"id": 2,
Expand Down
Loading