Skip to content

Commit 074f8f0

Browse files
authored
Implemented base commit expiration validation (#4849)
Implemented base commit expiration validation To turn feature on `base_commit_allowed_days` have to be added into https://github.com/flutter/.github/blob/main/autosubmit/autosubmit.yml Issue: [170476](flutter/flutter#170476)
1 parent f4ff6a2 commit 074f8f0

8 files changed

+397
-24
lines changed

auto_submit/lib/configuration/repository_configuration.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class RepositoryConfiguration {
2121
static const String supportNoReviewRevertKey = 'support_no_review_revert';
2222
static const String requiredCheckRunsOnRevertKey =
2323
'required_checkruns_on_revert';
24+
static const String baseCommitAllowedDaysKey = 'base_commit_allowed_days';
2425

2526
static const String defaultBranchStr = 'default';
2627

@@ -33,14 +34,16 @@ class RepositoryConfiguration {
3334
bool? runCi,
3435
bool? supportNoReviewReverts,
3536
Set<String>? requiredCheckRunsOnRevert,
37+
int? baseCommitAllowedDays,
3638
}) : allowConfigOverride = allowConfigOverride ?? false,
3739
defaultBranch = defaultBranch ?? defaultBranchStr,
3840
autoApprovalAccounts = autoApprovalAccounts ?? <String>{},
3941
approvingReviews = approvingReviews ?? 2,
4042
approvalGroup = approvalGroup ?? 'flutter-hackers',
4143
runCi = runCi ?? true,
4244
supportNoReviewReverts = supportNoReviewReverts ?? true,
43-
requiredCheckRunsOnRevert = requiredCheckRunsOnRevert ?? <String>{};
45+
requiredCheckRunsOnRevert = requiredCheckRunsOnRevert ?? <String>{},
46+
baseCommitAllowedDays = baseCommitAllowedDays ?? 0;
4447

4548
/// This flag allows the repository to override the org level configuration.
4649
bool allowConfigOverride;
@@ -71,6 +74,10 @@ class RepositoryConfiguration {
7174
/// merged.
7275
Set<String> requiredCheckRunsOnRevert;
7376

77+
/// The number of days that the base commit of the pull request can not be
78+
/// older than. If less than 1 then it will not be checked.
79+
int baseCommitAllowedDays;
80+
7481
@override
7582
String toString() {
7683
final stringBuffer = StringBuffer();
@@ -88,6 +95,7 @@ class RepositoryConfiguration {
8895
for (var checkrun in requiredCheckRunsOnRevert) {
8996
stringBuffer.writeln(' - $checkrun');
9097
}
98+
stringBuffer.writeln('$baseCommitAllowedDaysKey: $baseCommitAllowedDays');
9199
return stringBuffer.toString();
92100
}
93101

@@ -125,6 +133,7 @@ class RepositoryConfiguration {
125133
runCi: yamlDoc[runCiKey] as bool?,
126134
supportNoReviewReverts: yamlDoc[supportNoReviewRevertKey] as bool?,
127135
requiredCheckRunsOnRevert: requiredCheckRunsOnRevert,
136+
baseCommitAllowedDays: yamlDoc[baseCommitAllowedDaysKey] as int?,
128137
);
129138
}
130139
}

auto_submit/lib/configuration/repository_configuration_manager.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ class RepositoryConfigurationManager {
141141
runCi: globalConfiguration.runCi,
142142
supportNoReviewReverts: globalConfiguration.supportNoReviewReverts,
143143
requiredCheckRunsOnRevert: globalConfiguration.requiredCheckRunsOnRevert,
144+
baseCommitAllowedDays: globalConfiguration.baseCommitAllowedDays,
144145
);
145146

146147
// auto approval accounts, they should be empty if nothing was defined
@@ -186,6 +187,15 @@ class RepositoryConfigurationManager {
186187
);
187188
}
188189

190+
// if the local configuration is not the default value then use it.
191+
// Can be setted to negative value in local configuration to turn-off
192+
// base commit allowed days validation.
193+
final localBaseCommitAllowedDays = localConfiguration.baseCommitAllowedDays;
194+
if (localBaseCommitAllowedDays != 0) {
195+
mergedRepositoryConfiguration.baseCommitAllowedDays =
196+
localConfiguration.baseCommitAllowedDays;
197+
}
198+
189199
return mergedRepositoryConfiguration;
190200
}
191201
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:cocoon_server/logging.dart';
6+
import 'package:github/github.dart' as github;
7+
8+
import '../model/auto_submit_query_result.dart';
9+
import 'validation.dart';
10+
11+
/// Validates that the base of the PR commit is not older than a specified in
12+
/// configuration number of days.
13+
class BaseCommitDateAllowed extends Validation {
14+
BaseCommitDateAllowed({required super.config});
15+
16+
@override
17+
String get name => 'BaseCommitDateAllowed';
18+
19+
@override
20+
/// Implements the validation.
21+
Future<ValidationResult> validate(
22+
QueryResult result,
23+
github.PullRequest messagePullRequest,
24+
) async {
25+
final slug = messagePullRequest.base!.repo!.slug();
26+
final gitHubService = await config.createGithubService(slug);
27+
final sha = messagePullRequest.head!.sha!;
28+
final repositoryConfiguration = await config.getRepositoryConfiguration(
29+
slug,
30+
);
31+
32+
// If the base commit allowed days is less than or equal to 0 then the
33+
// validation is turned off and the base commit creation date is ignored.
34+
if (repositoryConfiguration.baseCommitAllowedDays <= 0) {
35+
log.info(
36+
'PR base commit creation date validation turned off by setting '
37+
'base_commit_allowed_days to ${repositoryConfiguration.baseCommitAllowedDays}.',
38+
);
39+
return ValidationResult(
40+
true,
41+
Action.IGNORE_FAILURE,
42+
'PR base commit creation date validation turned off by setting '
43+
'base_commit_allowed_days to ${repositoryConfiguration.baseCommitAllowedDays}.',
44+
);
45+
}
46+
47+
// If the base commit creation date is null then the validation fails but
48+
// should not block the PR merging.
49+
final commit = await gitHubService.getCommit(slug, sha);
50+
if (commit.commit?.author?.date == null) {
51+
log.info(
52+
'PR ${slug.fullName}/${messagePullRequest.number} base commit creation '
53+
'date is null.',
54+
);
55+
return ValidationResult(
56+
false,
57+
Action.IGNORE_FAILURE,
58+
'Could not find the base commit creation date of the PR '
59+
'${slug.fullName}/${messagePullRequest.number}.',
60+
);
61+
}
62+
63+
log.info(
64+
'PR ${slug.fullName}/${messagePullRequest.number} base creation date is: '
65+
'${commit.commit?.author?.date}. Allowed age is '
66+
'${repositoryConfiguration.baseCommitAllowedDays} days.',
67+
);
68+
69+
final isBaseRecent = commit.commit!.author!.date!.isAfter(
70+
DateTime.now().subtract(
71+
Duration(days: repositoryConfiguration.baseCommitAllowedDays),
72+
),
73+
);
74+
75+
final message = isBaseRecent
76+
? 'The base commit of the PR is recent enough for merging.'
77+
: 'The base commit of the PR is older than '
78+
'${repositoryConfiguration.baseCommitAllowedDays} days and can not '
79+
'be merged. Please merge the latest changes from the main into '
80+
'this branch and resubmit the PR.';
81+
82+
return ValidationResult(isBaseRecent, Action.REMOVE_LABEL, message);
83+
}
84+
}

auto_submit/lib/validations/validation_filter.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import '../configuration/repository_configuration.dart';
66
import '../service/config.dart';
77
import '../service/process_method.dart';
88
import 'approval.dart';
9+
import 'base_commit_date_allowed.dart';
910
import 'ci_successful.dart';
1011
import 'empty_checks.dart';
1112
import 'mergeable.dart';
@@ -47,6 +48,7 @@ class PullRequestValidationFilter implements ValidationFilter {
4748
Set<Validation> getValidations() {
4849
final validationsToRun = <Validation>{};
4950

51+
validationsToRun.add(BaseCommitDateAllowed(config: config));
5052
validationsToRun.add(Approval(config: config));
5153
// If we are running ci then we need to check the checkRuns and make sure
5254
// there are check runs created.

auto_submit/test/configuration/repository_configuration_data.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ const String sampleConfigWithOverride = '''
4545
support_no_review_revert: true
4646
required_checkruns_on_revert:
4747
- ci.yaml validation
48+
base_commit_allowed_days: 7
4849
''';

auto_submit/test/configuration/repository_configuration_manager_test.dart

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ void main() {
5151
- Google-testing
5252
- test (ubuntu-latest, 2.18.0)
5353
- cla/google
54+
base_commit_allowed_days: 10
5455
''';
5556

5657
githubService.fileContentsMockList.add(sampleConfig);
@@ -92,6 +93,7 @@ void main() {
9293
repositoryConfiguration.requiredCheckRunsOnRevert.contains('cla/google'),
9394
isTrue,
9495
);
96+
expect(repositoryConfiguration.baseCommitAllowedDays, 10);
9597
});
9698

9799
test(
@@ -189,6 +191,7 @@ void main() {
189191
),
190192
isTrue,
191193
);
194+
expect(repositoryConfiguration.baseCommitAllowedDays, 0);
192195
});
193196

194197
test('Default branch collected if omitted main', () async {
@@ -204,6 +207,7 @@ void main() {
204207
required_checkruns_on_revert:
205208
- ci.yaml validation
206209
- Google-testing
210+
base_commit_allowed_days: 7
207211
''';
208212

209213
githubService.fileContentsMockList.add(sampleConfig);
@@ -236,6 +240,7 @@ void main() {
236240
),
237241
isTrue,
238242
);
243+
expect(repositoryConfiguration.baseCommitAllowedDays, 7);
239244
});
240245

241246
group('Merging configurations tests', () {
@@ -253,6 +258,7 @@ void main() {
253258
support_no_review_revert: true
254259
required_checkruns_on_revert:
255260
- ci.yaml validation
261+
base_commit_allowed_days: 7
256262
*/
257263

258264
test('Global config merged with default local config', () {
@@ -296,6 +302,7 @@ void main() {
296302
),
297303
isTrue,
298304
);
305+
expect(mergedRepositoryConfiguration.baseCommitAllowedDays, 7);
299306
});
300307

301308
test('Auto approval accounts is additive, they cannot be removed', () {
@@ -398,29 +405,59 @@ void main() {
398405
);
399406
});
400407

401-
test('Approving reviews is not overridden if less than global config', () {
402-
const expectedApprovingReviews = 2;
403-
final localRepositoryConfiguration = RepositoryConfiguration(
404-
approvingReviews: 1,
405-
);
406-
final globalRepositoryConfiguration = RepositoryConfiguration.fromYaml(
407-
sampleConfigWithOverride,
408-
);
409-
final mergedRepositoryConfiguration = repositoryConfigurationManager
410-
.mergeConfigurations(
411-
globalRepositoryConfiguration,
412-
localRepositoryConfiguration,
413-
);
414-
expect(
415-
globalRepositoryConfiguration.approvingReviews ==
416-
mergedRepositoryConfiguration.approvingReviews,
417-
isTrue,
418-
);
419-
expect(
420-
mergedRepositoryConfiguration.approvingReviews,
421-
expectedApprovingReviews,
422-
);
423-
});
408+
test(
409+
'Base commit recent days overridden by local config if value is not 0',
410+
() {
411+
const expectedBaseCommitAllowedDays = -1;
412+
final localRepositoryConfiguration = RepositoryConfiguration(
413+
baseCommitAllowedDays: expectedBaseCommitAllowedDays,
414+
);
415+
final globalRepositoryConfiguration = RepositoryConfiguration.fromYaml(
416+
sampleConfigWithOverride,
417+
);
418+
final mergedRepositoryConfiguration = repositoryConfigurationManager
419+
.mergeConfigurations(
420+
globalRepositoryConfiguration,
421+
localRepositoryConfiguration,
422+
);
423+
expect(
424+
globalRepositoryConfiguration.baseCommitAllowedDays !=
425+
mergedRepositoryConfiguration.baseCommitAllowedDays,
426+
isTrue,
427+
);
428+
expect(
429+
mergedRepositoryConfiguration.baseCommitAllowedDays,
430+
expectedBaseCommitAllowedDays,
431+
);
432+
},
433+
);
434+
435+
test(
436+
'Base commit recent days is not overridden by local config if value is 0',
437+
() {
438+
const expectedBaseCommitAllowedDays = 7;
439+
final localRepositoryConfiguration = RepositoryConfiguration(
440+
baseCommitAllowedDays: 0,
441+
);
442+
final globalRepositoryConfiguration = RepositoryConfiguration.fromYaml(
443+
sampleConfigWithOverride,
444+
);
445+
final mergedRepositoryConfiguration = repositoryConfigurationManager
446+
.mergeConfigurations(
447+
globalRepositoryConfiguration,
448+
localRepositoryConfiguration,
449+
);
450+
expect(
451+
globalRepositoryConfiguration.baseCommitAllowedDays ==
452+
mergedRepositoryConfiguration.baseCommitAllowedDays,
453+
isTrue,
454+
);
455+
expect(
456+
mergedRepositoryConfiguration.baseCommitAllowedDays,
457+
expectedBaseCommitAllowedDays,
458+
);
459+
},
460+
);
424461

425462
test('Approval group is overridden if defined', () {
426463
const expectedApprovalGroup = 'flutter-devs';

0 commit comments

Comments
 (0)