| // Copyright 2025 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:cocoon_server/logging.dart'; | |
| import 'package:github/github.dart' as github; | |
| import '../model/auto_submit_query_result.dart'; | |
| import 'validation.dart'; | |
| /// Validates that the base of the PR commit is not older than a specified in | |
| /// configuration number of days. | |
| class BaseCommitDateAllowed extends Validation { | |
| BaseCommitDateAllowed({required super.config}); | |
| @override | |
| String get name => 'BaseCommitDateAllowed'; | |
| @override | |
| /// Implements the validation. | |
| Future<ValidationResult> validate( | |
| QueryResult result, | |
| github.PullRequest messagePullRequest, | |
| ) async { | |
| final slug = messagePullRequest.base!.repo!.slug(); | |
| final gitHubService = await config.createGithubService(slug); | |
| final sha = messagePullRequest.base!.sha!; | |
| final repositoryConfiguration = await config.getRepositoryConfiguration( | |
| slug, | |
| ); | |
| // If the base_commit_expiration is empty then the validation is turned off | |
| // and the base commit creation date is ignored. | |
| if (repositoryConfiguration.stalePrProtectionInDaysForBaseRefs.isEmpty) { | |
| log.info('PR base commit creation date validation turned off'); | |
| return ValidationResult( | |
| true, | |
| Action.IGNORE_FAILURE, | |
| 'PR base commit creation date validation turned off', | |
| ); | |
| } | |
| // Check if PR base expiration validation is configured for this branch. | |
| if (!repositoryConfiguration.stalePrProtectionInDaysForBaseRefs.containsKey( | |
| '${slug.fullName}/${messagePullRequest.base!.ref}', | |
| )) { | |
| log.info( | |
| 'PR ${slug.fullName}/${messagePullRequest.number} is for branch: ' | |
| '${messagePullRequest.base!.ref} which does not match any configured PR ' | |
| 'base for stale protection', | |
| ); | |
| return ValidationResult( | |
| true, | |
| Action.IGNORE_FAILURE, | |
| 'The base commit expiration validation is not configured for this ' | |
| 'branch.', | |
| ); | |
| } | |
| // If the base commit creation date is null then the validation fails but | |
| // should not block the PR merging. | |
| final commit = await gitHubService.getCommit(slug, sha); | |
| if (commit.commit?.author?.date == null) { | |
| log.info( | |
| 'PR ${slug.fullName}/${messagePullRequest.number} base commit creation ' | |
| 'date is null.', | |
| ); | |
| return ValidationResult( | |
| false, | |
| Action.IGNORE_FAILURE, | |
| 'Could not find the base commit creation date of the PR ' | |
| '${slug.fullName}/${messagePullRequest.number}.', | |
| ); | |
| } | |
| log.info( | |
| 'PR ${slug.fullName}/${messagePullRequest.number} requested for branch: ' | |
| '${messagePullRequest.base!.ref} with base creation date: ' | |
| '${commit.commit?.author?.date}.', | |
| ); | |
| final isBaseRecent = commit.commit!.author!.date!.isAfter( | |
| DateTime.now().subtract( | |
| Duration( | |
| days: repositoryConfiguration | |
| .stalePrProtectionInDaysForBaseRefs['${slug.fullName}/${messagePullRequest.base!.ref}']!, | |
| ), | |
| ), | |
| ); | |
| final message = isBaseRecent | |
| ? 'The base commit of the PR is recent enough for merging.' | |
| : 'The base commit of the PR is older than ' | |
| '${repositoryConfiguration.stalePrProtectionInDaysForBaseRefs['${slug.fullName}/${messagePullRequest.base!.ref}']} ' | |
| 'days and can not be merged. Please merge the latest changes ' | |
| 'from the main into this branch and resubmit the PR.'; | |
| return ValidationResult(isBaseRecent, Action.REMOVE_LABEL, message); | |
| } | |
| } |