blob: c7e3932665bdfd1864e71ba003866c888224cf34 [file] [log] [blame]
// Copyright 2022 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:auto_submit/model/auto_submit_query_result.dart' as auto;
import 'package:auto_submit/service/github_service.dart';
import 'package:auto_submit/validations/validation.dart';
import 'package:github/github.dart' as github;
import '../service/log.dart';
class Revert extends Validation {
Revert({
required super.config,
});
static const Set<String> allowedReviewers = <String>{ORG_MEMBER, ORG_OWNER};
@override
Future<ValidationResult> validate(auto.QueryResult result, github.PullRequest messagePullRequest) async {
final auto.PullRequest pullRequest = result.repository!.pullRequest!;
final String authorAssociation = pullRequest.authorAssociation!;
final String? author = pullRequest.author!.login;
if (!isValidAuthor(author, authorAssociation)) {
String message = 'The author $author does not have permissions to make this request.';
log.info(message);
return ValidationResult(false, Action.REMOVE_LABEL, message);
}
bool? canMerge = messagePullRequest.mergeable;
if (canMerge == null || !canMerge) {
String message =
'This pull request cannot be merged due to conflicts. Please resolve conflicts and re-add the revert label.';
log.info(message);
return ValidationResult(false, Action.REMOVE_LABEL, message);
}
String? pullRequestBody = messagePullRequest.body;
String? revertLink = extractLinkFromText(pullRequestBody);
if (revertLink == null) {
String message =
'A reverts link could not be found or was formatted incorrectly. Format is \'Reverts owner/repo#id\'';
log.info(message);
return ValidationResult(false, Action.REMOVE_LABEL, message);
}
github.RepositorySlug repositorySlug = _getSlugFromLink(revertLink);
GithubService githubService = await config.createGithubService(repositorySlug);
int pullRequestId = _getPullRequestNumberFromLink(revertLink);
github.PullRequest requestToRevert = await githubService.getPullRequest(repositorySlug, pullRequestId);
bool requestsMatch = await githubService.comparePullRequests(repositorySlug, requestToRevert, messagePullRequest);
if (requestsMatch) {
return ValidationResult(
true,
Action.IGNORE_FAILURE,
'Revert request has been verified and will be queued for merge.',
);
}
return ValidationResult(
false,
Action.REMOVE_LABEL,
'Validation of the revert request has failed. Verify the files in the revert request are the same as the original PR and resubmit the revert request.',
);
}
/// Only a team member and code owner can submit a revert request without a review.
bool isValidAuthor(String? author, String authorAssociation) {
return config.rollerAccounts.contains(author) || allowedReviewers.contains(authorAssociation);
}
/// The full text here is 'Reverts flutter/cocoon#XXXXX' as output by github
/// the link must be in the form github.com/flutter/repo/pull/id
String? extractLinkFromText(String? bodyText) {
if (bodyText == null) {
return null;
}
final RegExp regExp = RegExp(r'^[Rr]everts[\s]+([-\.a-zA-Z_]+/[-\.a-zA-Z_]+#[0-9]+)$', multiLine: true);
Iterable<RegExpMatch> matches = regExp.allMatches(bodyText);
if (matches.isNotEmpty) {
return matches.elementAt(0).group(1);
}
return null;
}
/// Split a reverts link on the '#' then the '/' to get the parts of the repo
/// slug. It is assumed that the link has the format flutter/repo#id.
github.RepositorySlug _getSlugFromLink(String link) {
List<String> linkSplit = link.split('#');
List<String> slugSplit = linkSplit.elementAt(0).split('/');
return github.RepositorySlug(slugSplit.elementAt(0), slugSplit.elementAt(1));
}
/// Split a reverts link on the '#' to get the id part of the link.
/// It is assumed that the link has the format flutter/repo#id.
int _getPullRequestNumberFromLink(String link) {
List<String> linkSplit = link.split('#');
return int.parse(linkSplit.elementAt(1));
}
}