From 1e685160ea60cc24f7db36bdc0c4f073c2e61ec9 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 27 Apr 2026 13:30:52 -0700 Subject: [PATCH] Unlock Flutter Presubmits and Merge Queue Guard when emergency label is added. This addresses https://github.com/flutter/flutter/issues/185628 --- .../github/webhook_subscription.dart | 71 ++++---- .../github/webhook_subscription_test.dart | 160 ++++++++++++++---- 2 files changed, 160 insertions(+), 71 deletions(-) diff --git a/app_dart/lib/src/request_handlers/github/webhook_subscription.dart b/app_dart/lib/src/request_handlers/github/webhook_subscription.dart index fb94076a7..2ab49a5d4 100644 --- a/app_dart/lib/src/request_handlers/github/webhook_subscription.dart +++ b/app_dart/lib/src/request_handlers/github/webhook_subscription.dart @@ -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 _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 _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 { @@ -1171,4 +1142,34 @@ The "Merge" button is also unlocked. To bypass presubmits as well as the tree st ); } } + + Future _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.'); + } } diff --git a/app_dart/test/request_handlers/github/webhook_subscription_test.dart b/app_dart/test/request_handlers/github/webhook_subscription_test.dart index 854a6c1c0..fc09e7ff5 100644 --- a/app_dart/test/request_handlers/github/webhook_subscription_test.dart +++ b/app_dart/test/request_handlers/github/webhook_subscription_test.dart @@ -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, @@ -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, @@ -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 } @@ -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.', + ), + ), ]), ), ); @@ -3153,7 +3241,7 @@ void foo() { ); githubService.createdComments.clear(); githubService.checkRunsMock = '''{ - "total_count": 2, + "total_count": 1, "check_runs": [ { "id": 2, @@ -3197,7 +3285,7 @@ void foo() { ); githubService.createdComments.clear(); githubService.checkRunsMock = '''{ - "total_count": 2, + "total_count": 1, "check_runs": [ { "id": 2,