Skip to content

Commit

Permalink
Implement enqueuing of pull requests for MQ-enabled repos (#4000)
Browse files Browse the repository at this point in the history
  • Loading branch information
yjbanov authored Nov 1, 2024
1 parent 1d6abd2 commit 19e03e5
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 14 deletions.
2 changes: 1 addition & 1 deletion auto_submit/lib/model/auto_submit_query_result.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion auto_submit/lib/model/big_query_pull_request_record.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions auto_submit/lib/requests/graphql_queries.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,61 @@ mutation RevertPullFlutterPullRequest ($revertBody:String!, $clientMutationId:St
}
''');
}

/// Instructs Github to put a pull request in the merge queue.
///
/// Assumes the repository targeted by the pull request has merge queue enabled.
///
/// https://docs.github.com/en/graphql/reference/mutations#enqueuepullrequest
class EnqueuePullRequestMutation extends GraphQLOperation {
EnqueuePullRequestMutation({
required this.id,
required this.expectedHeadOid,
required this.jump,
this.clientMutationId,
});

final String? clientMutationId;
final String id;
final String expectedHeadOid;
final bool jump;

@override
Map<String, dynamic> get variables => {
'clientMutationId': clientMutationId,
'pullRequestId': id,
'expectedHeadOid': expectedHeadOid,
'jump': jump,
};

@override
DocumentNode get documentNode => lang.parseString(r'''
mutation EnqueueFlutterPullRequest ($clientMutationId:String!, $pullRequestId:ID!, $expectedHeadOid:GitObjectID!, $jump:Boolean!) {
enqueuePullRequest (
input: {
clientMutationId: $clientMutationId,
expectedHeadOid: $expectedHeadOid,
jump: $jump,
pullRequestId: $pullRequestId,
}
) {
clientMutationId
pullRequest {
author {
login
}
authorAssociation
id
title
number
repository {
owner {
login
}
name
}
}
}
}
''');
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class PullRequestValidationService extends ValidationService {
}

// If we got to this point it means we are ready to submit the PR.
final MergeResult processed = await processMerge(
final MergeResult processed = await submitPullRequest(
config: config,
messagePullRequest: messagePullRequest,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ class RevertRequestValidationService extends ValidationService {
}

// If we got to this point it means we are ready to submit the PR.
final MergeResult processed = await processMerge(
final MergeResult processed = await submitPullRequest(
config: config,
messagePullRequest: messagePullRequest,
);
Expand Down
47 changes: 45 additions & 2 deletions auto_submit/lib/service/validation_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ValidationService {
}

/// Merges the commit if the PullRequest passes all the validations.
Future<MergeResult> processMerge({
Future<MergeResult> submitPullRequest({
required Config config,
required github.PullRequest messagePullRequest,
}) async {
Expand All @@ -81,6 +81,49 @@ ${messagePullRequest.title!.replaceFirst('Revert "Revert', 'Reland')}
final String prBody = _sanitizePrBody(messagePullRequest.body ?? '');
final String commitMessage = '$messagePrefix$prBody';

// TODO(yjbanov): figure out how to determine if the repo is MQ-enabled.
final bool isMergeQueueEnabled = slug.fullName == 'flutter/flaux';

if (isMergeQueueEnabled) {
return _enqueuePullRequest(slug, messagePullRequest);
} else {
return _mergePullRequest(number, commitMessage, slug);
}
}

Future<MergeResult> _enqueuePullRequest(github.RepositorySlug slug, github.PullRequest pullRequest) async {
final graphQlService = GraphQlService();
final graphQLClient = await config.createGitHubGraphQLClient(slug);

final isEmergencyPullRequest = pullRequest.labels?.where((label) => label.name == 'emergency').isNotEmpty ?? false;

final enqueueMutation = EnqueuePullRequestMutation(
id: pullRequest.id!.toString(),
expectedHeadOid: pullRequest.head!.ref!,
jump: isEmergencyPullRequest,
);

try {
await retryOptions.retry(
() async {
await graphQlService.mutateGraphQL(
documentNode: enqueueMutation.documentNode,
variables: enqueueMutation.variables,
client: graphQLClient,
);
},
retryIf: (Exception e) => e is RetryableException,
);
} catch (e) {
final message = 'Failed to enqueue ${slug.fullName}/${pullRequest.number} with $e';
log.severe(message);
return (result: false, message: message);
}

return (result: true, message: pullRequest.title!);
}

Future<MergeResult> _mergePullRequest(int number, String commitMessage, github.RepositorySlug slug) async {
try {
github.PullRequestMerge? result;

Expand All @@ -106,7 +149,7 @@ ${messagePullRequest.title!.replaceFirst('Revert "Revert', 'Reland')}
}
} catch (e) {
// Catch graphql client init exceptions.
final String message = 'Failed to merge ${slug.fullName}/$number with ${e.toString()}';
final String message = 'Failed to merge ${slug.fullName}/$number with $e';
log.severe(message);
return (result: false, message: message);
}
Expand Down
130 changes: 126 additions & 4 deletions auto_submit/test/service/pull_request_validation_service_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:auto_submit/service/pull_request_validation_service.dart';
import 'package:auto_submit/service/validation_service.dart';
import 'package:github/github.dart';
import 'package:googleapis/bigquery/v2.dart';
import 'package:graphql/client.dart';
import 'package:mockito/mockito.dart';
import 'package:retry/retry.dart';
import 'package:test/test.dart';
Expand Down Expand Up @@ -216,7 +217,7 @@ void main() {
});
});

group('processMerge', () {
group('submitPullRequest', () {
test('Correct PR titles when merging to use Reland', () async {
final PullRequest pullRequest = generatePullRequest(
prNumber: 0,
Expand All @@ -230,7 +231,7 @@ void main() {
sha: pullRequest.mergeCommitSha,
);

final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand Down Expand Up @@ -371,7 +372,7 @@ void main() {
merged: true,
sha: pullRequest.mergeCommitSha,
);
final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand Down Expand Up @@ -430,7 +431,7 @@ If you need help, consider asking for advice on the #hackers-new channel on [Dis
sha: pullRequest.mergeCommitSha,
);

final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand All @@ -442,5 +443,126 @@ Various bugfixes and performance improvements.
Fixes #12345 and #3.
This is the second line in a paragraph.''');
});

test('Enqueues pull request when merge queue is used', () async {
slug = RepositorySlug('flutter', 'flaux');
final prTitle = 'This pull request should be enqueueueueueueueueueueued';

Map<String, Object?>? mutationOptions;
githubGraphQLClient.mutateResultForOptions = (MutationOptions options) {
mutationOptions = options.variables;
return QueryResult(
options: options,
source: QueryResultSource.network,
data: {},
);
};

final PullRequest pullRequest = generatePullRequest(
prNumber: 0,
repoName: slug.name,
title: prTitle,
mergeable: true,
);

final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);

expect(
mutationOptions,
{
'clientMutationId': null,
'pullRequestId': '1',
'expectedHeadOid': 'new-topic',
'jump': false,
},
);
expect(result.result, isTrue);
expect(result.message, contains(prTitle));
});

test('Fails to enqueue pull request when merge queue is used', () async {
slug = RepositorySlug('flutter', 'flaux');
final prTitle = 'This pull request should fail to enqueueueueueueueueueu';

Map<String, Object?>? mutationOptions;
githubGraphQLClient.mutateResultForOptions = (MutationOptions options) {
mutationOptions = options.variables;
return QueryResult(
options: options,
source: QueryResultSource.network,
exception: OperationException(),
);
};

final PullRequest pullRequest = generatePullRequest(
prNumber: 42,
repoName: slug.name,
title: prTitle,
mergeable: true,
);

final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);

expect(
mutationOptions,
{
'clientMutationId': null,
'pullRequestId': '1',
'expectedHeadOid': 'new-topic',
'jump': false,
},
);
expect(result.result, isFalse);
expect(
result.message,
contains('Failed to enqueue flutter/flaux/42 with HTTP 400: GraphQL mutate failed'),
);
});

test('Jumps the queue for emergency pull requests', () async {
slug = RepositorySlug('flutter', 'flaux');
final prTitle = 'This pull request should fail to enqueueueueueueueueueu';

Map<String, Object?>? mutationOptions;
githubGraphQLClient.mutateResultForOptions = (MutationOptions options) {
mutationOptions = options.variables;
return QueryResult(
options: options,
source: QueryResultSource.network,
data: {},
);
};

final PullRequest pullRequest = generatePullRequest(
prNumber: 42,
repoName: slug.name,
title: prTitle,
mergeable: true,
labelName: 'emergency',
);

final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);

expect(
mutationOptions,
{
'clientMutationId': null,
'pullRequestId': '1',
'expectedHeadOid': 'new-topic',
'jump': true,
},
);
expect(result.result, isTrue);
expect(result.message, contains(prTitle));
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ Reason for reverting: comment was added by mistake.''';
});
});

group('processMerge:', () {
group('submitPullRequest:', () {
test('Correct PR titles when merging to use Reland', () async {
final PullRequest pullRequest = generatePullRequest(
prNumber: 0,
Expand All @@ -1267,7 +1267,7 @@ Reason for reverting: comment was added by mistake.''';
sha: pullRequest.mergeCommitSha,
);

final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand All @@ -1291,7 +1291,7 @@ Reason for reverting: comment was added by mistake.''';
merged: true,
sha: pullRequest.mergeCommitSha,
);
final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand Down Expand Up @@ -1350,7 +1350,7 @@ If you need help, consider asking for advice on the #hackers-new channel on [Dis
sha: pullRequest.mergeCommitSha,
);

final MergeResult result = await validationService.processMerge(
final MergeResult result = await validationService.submitPullRequest(
config: config,
messagePullRequest: pullRequest,
);
Expand Down
Loading

0 comments on commit 19e03e5

Please sign in to comment.