blob: f22ebfbec7c655323b8fca045b5ab2ff086ead7c [file] [edit]
// Copyright 2026 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 'dart:convert';
import 'dart:math' as math;
import 'package:cocoon_common/task_status.dart';
import 'package:cocoon_integration_test/cocoon_integration_test.dart';
import 'package:cocoon_service/cocoon_service.dart';
import 'package:github/github.dart';
import 'package:googleapis/firestore/v1.dart' as g;
import 'package:uuid/uuid.dart';
import 'cocoon.dart';
import 'scenarios.dart';
/// Seeder to populate [IntegrationServer] with fake data.
class DataSeeder {
DataSeeder(this._server, {this.scenario = Scenario.realistic});
final IntegrationServer _server;
final Scenario scenario;
/// Seeds the [IntegrationServer] with initial data.
void seed({DateTime? now}) {
if (scenario == Scenario.empty) {
return;
}
// Use a fixed seed for reproducibility in tests, matching DevelopmentCocoonService
now ??= DateTime.now();
final random = math.Random(now.millisecondsSinceEpoch);
// Seed Commits and Tasks
for (final repo in ['flutter', 'cocoon', 'packages']) {
final branch = defaultBranches[repo]!;
_seedCommitStatuses(now, random, repo, branch);
}
// Seed Tree Status Changes
_seedTreeStatusChanges(now);
// Seed Suppressed Tests
_seedSuppressedTests(now);
// Seed Presubmit Data
_seedPresubmitData(now);
}
void _seedPresubmitData(DateTime now) {
final guards = <PresubmitGuard>[];
final checks = <PresubmitJob>[];
// cafe5_1_mock_sha
final prNum = 1234;
var checkRunId = 123456;
var creationTime = 1770000000000;
final enginejobs = [
'Mac mac_host_engine',
'Mac mac_ios_engine',
'Linux linux_android_aot_engine',
];
final fusionjobs = [
'Linux framework_tests',
'Mac framework_tests',
'Linux android framework_tests',
'Windows framework_tests',
'Mac_x64 framework_misc',
'Linux_android_emu android_engine_opengles_tests',
'Linux_android_emu_vulkan_stable android_engine_vulkan_tests',
'Mac_arm64 run_debug_test_macos',
'Linux_android_emu android views',
];
var engineChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[0],
status: TaskStatus.failed,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[1],
status: TaskStatus.cancelled,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[2],
status: TaskStatus.infraFailure,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'cafe5_1_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[0],
stage: CiStage.fusionEngineBuild,
creationTime: creationTime,
jobs: {for (var check in engineChecks) check.jobName: check.status},
),
);
checks.addAll(engineChecks);
// face5_2_mock_sha
checkRunId = 234567;
creationTime = creationTime + 100000;
engineChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[0],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[1],
status: TaskStatus.skipped,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[2],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'face5_2_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[1],
stage: CiStage.fusionEngineBuild,
creationTime: creationTime,
jobs: _getLatestjobstatuses(engineChecks),
),
);
checks.addAll(engineChecks);
creationTime = creationTime + 100000;
var fusionChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[0],
status: TaskStatus.inProgress,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[1],
status: TaskStatus.skipped,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[2],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[3],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'face5_2_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[1],
stage: CiStage.fusionTests,
creationTime: creationTime,
jobs: _getLatestjobstatuses(fusionChecks),
),
);
checks.addAll(fusionChecks);
// decaf_3_mock_sha
checkRunId = 345678;
creationTime = creationTime + 100000;
engineChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[0],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[1],
status: TaskStatus.skipped,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[2],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'decaf_3_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[3],
stage: CiStage.fusionEngineBuild,
creationTime: creationTime,
jobs: _getLatestjobstatuses(engineChecks),
),
);
checks.addAll(engineChecks);
creationTime = creationTime + 100000;
var creationTime2 = creationTime + 100000;
fusionChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[0],
status: TaskStatus.failed,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[0],
status: TaskStatus.succeeded,
attemptNumber: 2,
creationTime: creationTime2,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[1],
status: TaskStatus.skipped,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[2],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[3],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'decaf_3_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[3],
stage: CiStage.fusionTests,
creationTime: creationTime,
jobs: _getLatestjobstatuses(fusionChecks),
),
);
checks.addAll(fusionChecks);
// deafcab_mock_sha
checkRunId = 456789;
creationTime = creationTime + 100000;
engineChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[0],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[1],
status: TaskStatus.skipped,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: enginejobs[2],
status: TaskStatus.succeeded,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'deafcab_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[4],
stage: CiStage.fusionEngineBuild,
creationTime: creationTime,
jobs: _getLatestjobstatuses(engineChecks),
),
);
checks.addAll(engineChecks);
creationTime = creationTime + 100000;
creationTime2 = creationTime + 100000;
fusionChecks = [
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[0],
status: TaskStatus.failed,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[0],
status: TaskStatus.succeeded,
attemptNumber: 2,
creationTime: creationTime2,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[1],
status: TaskStatus.cancelled,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[2],
status: TaskStatus.infraFailure,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[3],
status: TaskStatus.failed,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[3],
status: TaskStatus.failed,
attemptNumber: 2,
creationTime: creationTime2,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[4],
status: TaskStatus.neutral,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[5],
status: TaskStatus.neutral,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[6],
status: TaskStatus.neutral,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[7],
status: TaskStatus.neutral,
attemptNumber: 1,
creationTime: creationTime,
),
_createPresubmitJob(
checkRunId: checkRunId,
jobName: fusionjobs[8],
status: TaskStatus.neutral,
attemptNumber: 1,
creationTime: creationTime,
),
];
guards.add(
_createPresubmitGuard(
headSha: 'deafcab_mock_sha',
checkRunId: checkRunId,
prNum: prNum,
author: _authors[4],
stage: CiStage.fusionTests,
creationTime: creationTime,
jobs: _getLatestjobstatuses(fusionChecks),
),
);
checks.addAll(fusionChecks);
final prCheckRuns = <PrCheckRuns>[];
for (final guard in guards) {
prCheckRuns.add(_createPrCheckRuns(guard));
}
// Add some checks with multiple attempts for testing fetchPresubmitJobDetails
_server.firestore.putDocuments(guards);
_server.firestore.putDocuments(checks);
_server.firestore.putDocuments(prCheckRuns);
}
Map<String, TaskStatus> _getLatestjobstatuses(List<PresubmitJob> checks) {
final latestChecks = <String, PresubmitJob>{};
for (final check in checks) {
if (!latestChecks.containsKey(check.jobName) ||
check.attemptNumber > latestChecks[check.jobName]!.attemptNumber) {
latestChecks[check.jobName] = check;
}
}
return {for (var check in latestChecks.values) check.jobName: check.status};
}
PresubmitGuard _createPresubmitGuard({
required String headSha,
required int checkRunId,
required int prNum,
required String author,
required CiStage stage,
required int creationTime,
required Map<String, TaskStatus> jobs,
}) {
final slug = RepositorySlug('flutter', 'flutter');
final failedJobs = jobs.values.where((status) => status.isFailure).length;
final remainingJobs = jobs.values
.where((status) => !status.isComplete)
.length;
return PresubmitGuard(
checkRun: generateCheckRun(
checkRunId,
name: 'Merge Queue Guard',
startedAt: DateTime.fromMillisecondsSinceEpoch(creationTime),
),
headSha: headSha,
slug: slug,
prNum: prNum,
stage: stage,
creationTime: creationTime,
author: author,
remainingJobs: remainingJobs,
failedJobs: failedJobs,
jobs: jobs,
);
}
PresubmitJob _createPresubmitJob({
required int checkRunId,
required String jobName,
required TaskStatus status,
required int creationTime,
int attemptNumber = 1,
}) {
return PresubmitJob(
slug: RepositorySlug('flutter', 'flutter'),
checkRunId: checkRunId,
jobName: jobName,
status: status,
attemptNumber: attemptNumber,
creationTime: creationTime,
buildNumber: 1337 + attemptNumber,
buildId: 24567 + attemptNumber,
summary: switch (status) {
.succeeded =>
'[INFO] Starting task $jobName...\n[SUCCESS] All tests passed (452/452)',
.failed =>
'[INFO] Starting task $jobName...\n[ERROR] Test failed: Dummy Tests',
.infraFailure =>
'[INFO] Starting task $jobName...\n[ERROR] Infrastructure failure: Dummy Tests',
.cancelled =>
'[INFO] Starting task $jobName...\n[ERROR] Test cancelled: Dummy Tests',
.inProgress => '[INFO] Starting task $jobName...',
.waitingForBackfill => null,
.skipped =>
'[INFO] Starting task $jobName...\n[INFO] Test skipped: Dummy Tests',
.neutral =>
'[INFO] Starting task $jobName...\n[INFO] Test neutral: Dummy Tests',
},
startTime: creationTime + 30000,
endTime: creationTime + 60000,
logAnalysis: switch (status) {
.failed =>
'Based on my analysis of the provided LUCI logs and the context of the changes in this PR, here is the breakdown of the build failure:\n\n ### 1. Identify the specific test or command that failed for $jobName \n...',
_ => null,
},
);
}
PrCheckRuns _createPrCheckRuns(PresubmitGuard guard) {
final pr = PullRequest(
number: guard.prNum,
head: PullRequestHead(
sha: guard.commitSha,
repo: Repository(
fullName: guard.slug.fullName,
name: guard.slug.name,
owner: UserInformation(guard.slug.owner, 1, '', ''),
),
),
base: PullRequestHead(
ref: 'master',
repo: Repository(
fullName: guard.slug.fullName,
name: guard.slug.name,
owner: UserInformation(guard.slug.owner, 1, '', ''),
),
),
user: User(login: guard.author),
labels: [],
);
final docName =
'projects/${Config.flutterGcpProjectId}/databases/${Config.flutterGcpFirestoreDatabase}/documents/${PrCheckRuns.kCollectionId}/${const Uuid().v4()}';
final prCheckRuns = PrCheckRuns()
..pullRequest = pr
..fields['sha'] = guard.commitSha.toValue()
..fields['slug'] = jsonEncode(guard.slug.toJson()).toValue()
..fields['Merge Queue Guard'] = guard.checkRun.id!.toString().toValue()
..name = docName;
for (final jobName in guard.jobs.keys) {
prCheckRuns.fields[jobName] = '234567'.toString().toValue();
}
return prCheckRuns;
}
void _seedTreeStatusChanges(DateTime now) {
final changes = <TreeStatusChange>[];
// Create a history of tree status changes
for (var i = 0; i < 10; i++) {
changes.add(
_createTreeStatusChange(
i,
created: now.subtract(Duration(hours: i)),
status: i.isEven ? TreeStatus.success : TreeStatus.failure,
reason: i.isEven ? null : 'Build failure on Linux',
),
);
}
_server.firestore.putDocuments(changes);
}
void _seedSuppressedTests(DateTime now) {
final suppressed = <SuppressedTest>[];
suppressed.add(
_createSuppressedTest(
name: 'Linux_android 0',
created: now.subtract(const Duration(days: 1)),
issueLink: 'https://github.com/flutter/flutter/issues/12345',
),
);
_server.firestore.putDocuments(suppressed);
}
TreeStatusChange _createTreeStatusChange(
int i, {
DateTime? created,
TreeStatus status = TreeStatus.success,
String author = 'dash',
String? reason,
String repo = 'flutter',
}) {
final name =
'projects/${Config.flutterGcpProjectId}/databases/${Config.flutterGcpFirestoreDatabase}/documents/${TreeStatusChange.metadata.collectionId}/$i';
return TreeStatusChange.fromDocument(
g.Document(
name: name,
fields: {
'createTimestamp': (created ?? DateTime.fromMillisecondsSinceEpoch(i))
.toValue(),
'status': status.name.toValue(),
'author': author.toValue(),
'repository': RepositorySlug('flutter', repo).fullName.toValue(),
if (reason != null) 'reason': reason.toValue(),
},
),
);
}
SuppressedTest _createSuppressedTest({
String name = 'task',
String repository = 'flutter/flutter',
bool isSuppressed = true,
String issueLink = 'link',
DateTime? created,
}) {
final docName =
'projects/${Config.flutterGcpProjectId}/databases/${Config.flutterGcpFirestoreDatabase}/documents/${SuppressedTest.kCollectionId}/$name';
return SuppressedTest(
name: name,
repository: repository,
isSuppressed: isSuppressed,
issueLink: issueLink,
createTimestamp: created ?? DateTime.fromMillisecondsSinceEpoch(1),
)..name = docName;
}
void _seedCommitStatuses(
DateTime now,
math.Random random,
String repo,
String branch,
) {
const commitGap = 2 * 60 * 1000; // 2 minutes between commits
final baseTimestamp = now.millisecondsSinceEpoch;
final commits = <Commit>[];
final tasks = <Task>[];
final commitCount = scenario == Scenario.highLoad ? 100 : 25;
for (var index = 0; index < commitCount; index += 1) {
final commitTimestamp = baseTimestamp - ((index + 1) * commitGap);
// Use the same random sequence as DevelopmentCocoonService
final commitRandom = math.Random(commitTimestamp);
// Generate a stable and unique SHA for each commit
final headSha = _commitsSha[repo]![index];
final authorIndex = commitRandom.nextInt(_authors.length);
final messageSeed = commitTimestamp % 37 + authorIndex;
final messageInc = _messagePrimes[messageSeed % _messagePrimes.length];
final message = List<String>.generate(
6,
(int i) => _words[(messageSeed + i * messageInc) % _words.length],
).join(' ');
final commit = generateFirestoreCommit(
index,
sha: headSha,
repo: repo,
branch: branch,
createTimestamp: commitTimestamp,
author: _authors[authorIndex],
avatar: _avatars[authorIndex],
message: message,
);
commits.add(commit);
final taskCount = switch (repo) {
'flutter' => 100,
'packages' => 42,
'cocoon' => 13,
_ => 3,
};
for (var i = 0; i < taskCount; i++) {
tasks.add(
_createFakeTask(
now,
commitTimestamp,
index,
i,
commitRandom,
headSha,
repo,
),
);
}
}
_server.firestore.putDocuments(commits);
_server.firestore.putDocuments(tasks);
}
Task _createFakeTask(
DateTime now,
int commitTimestamp,
int commitIndex,
int taskIndex,
math.Random random,
String commitSha,
String repo,
) {
const commitGap = 2 * 60 * 1000;
final age = (now.millisecondsSinceEpoch - commitTimestamp) ~/ commitGap;
TaskStatus status;
if (scenario == Scenario.allGreen) {
status = TaskStatus.succeeded;
} else if (scenario == Scenario.redTree && commitIndex == 0) {
status = TaskStatus.failed;
} else {
// The [statusesProbability] list is an list of proportional
// weights to give each of the values in _statuses when randomly
// determining the status. So e.g. if one is 150, another 50, and
// the rest 0, then the first has a 75% chance of being picked,
// the second a 25% chance, and the rest a 0% chance.
final statusesProbability = <int>[
// bigger = more probable
math.max(taskIndex % 2, 20 - age * 2), // TaskStatus.waitingForBackfill
math.max(0, 10 - age * 2), // TaskStatus.inProgress
math.min(10 + age * 2, 100), // TaskStatus.succeeded
math.min(1 + age ~/ 3, 30), // TaskStatus.failed
if (taskIndex % 15 == 0) // TaskStatus.infraFailure
5
else if (taskIndex % 25 == 0)
15
else
1,
if (taskIndex % 20 == 0) 30,
1, // TaskStatus.cancelled
];
final maxProbability = statusesProbability.fold(
0,
(int c, int p) => c + p,
);
var weightedIndex = random.nextInt(maxProbability);
var statusIndex = 0;
while (weightedIndex > statusesProbability[statusIndex]) {
weightedIndex -= statusesProbability[statusIndex];
statusIndex += 1;
}
status = _statuses[statusIndex];
}
final minAttempts = _minAttempts[status]!;
final maxAttempts = _maxAttempts[status]!;
final attempts =
minAttempts + random.nextInt(maxAttempts - minAttempts + 1);
final buildNumber = attempts > 0 ? random.nextInt(1000) : null;
return generateFirestoreTask(
taskIndex,
name: 'Linux_android $taskIndex',
status: status,
attempts: attempts,
buildNumber: buildNumber,
commitSha: commitSha,
created: DateTime.fromMillisecondsSinceEpoch(commitTimestamp + taskIndex),
started: DateTime.fromMillisecondsSinceEpoch(
commitTimestamp + (taskIndex * 1000 * 60),
),
ended: DateTime.fromMillisecondsSinceEpoch(
commitTimestamp + (taskIndex * 1000 * 60) + (taskIndex * 1000 * 60),
),
bringup: taskIndex == now.millisecondsSinceEpoch % 13,
testFlaky: attempts > 1,
);
}
static const _statuses = [
TaskStatus.waitingForBackfill,
TaskStatus.inProgress,
TaskStatus.succeeded,
TaskStatus.failed,
TaskStatus.infraFailure,
TaskStatus.skipped,
TaskStatus.cancelled,
];
static const _minAttempts = {
TaskStatus.waitingForBackfill: 1,
TaskStatus.inProgress: 1,
TaskStatus.succeeded: 1,
TaskStatus.failed: 1,
TaskStatus.infraFailure: 1,
TaskStatus.skipped: 1,
TaskStatus.cancelled: 1,
};
static const _maxAttempts = {
TaskStatus.waitingForBackfill: 1,
TaskStatus.inProgress: 2,
TaskStatus.succeeded: 1,
TaskStatus.failed: 2,
TaskStatus.infraFailure: 2,
TaskStatus.skipped: 1,
TaskStatus.cancelled: 1,
};
static const List<String> _authors = <String>[
'matan',
'yegor',
'john',
'jenn',
'kate',
'stuart',
];
static const List<String> _avatars = [
'https://avatars.githubusercontent.com/u/168174?v=4',
'https://avatars.githubusercontent.com/u/211513?v=4',
'https://avatars.githubusercontent.com/u/1924313?v=4',
'https://avatars.githubusercontent.com/u/682784?v=4',
'https://avatars.githubusercontent.com/u/16964204?v=4',
'https://avatars.githubusercontent.com/u/122189?v=4',
];
static const List<int> _messagePrimes = <int>[
3,
11,
17,
23,
31,
41,
47,
67,
79,
];
static const List<String> _words = <String>[
'fixes',
'issue',
'crash',
'developer',
'blocker',
'intermittent',
'format',
];
static const _commitsSha = {
'cocoon': _commitsCocoon,
'flutter': _commitsFlutter,
'packages': _commitsPackages,
};
// These commits from from flutter/cocoon
static const List<String> _commitsCocoon = <String>[
'2fd76f920a38e4384248173d05ee482d5aeaf4c5',
'2d22b5e85f986f3fa2cf1bfaf085905c2182c270',
'77238bc7bf35489df03bc00ce2b2231a1afe6b06',
'01d87b7a802e6ea388a066e773b1af3dace44053',
'754039ae0cc524db1052da0f22c9275e32fe4f54',
'1f5e006a398fa5d0e59f78cd5071e2532d2fe438',
'a798b24044d567df62b8693b179932a8364c8dd8',
'4389a8a3dbe1ed4c6a643641e95d7759f2158d9e',
'c05e886884ec9adff2f43b87dbcb02e3507d971b',
'a2acc46447cdfcd6628a897dea27ff64849bfc99',
'd31d67ffb38fbd09ecf0a11ad5f6fd433cec9c9f',
'e303d2c71c956c9c1eb7bf81473aac20d756eb75',
'789f6bb335fe31de5d9c6adbab2fc169030a057f',
'c97f0670658e04f096a3e57b20fa8241306ffcaa',
'e8e31198861b5f53f04900bdf9a54e8bf6b7d597',
'434a0a7d3c4c3bc633e06de7707ef590c54c20c3',
'e7fca29b3f0408c7c1726e270a5aba0e28e74090',
'dd6f94d8573a77506a122899a0592a956ac57bec',
'cb19ec23d79b9d422f577722e5a14253fdcaea71',
'4cb8c6498aedfd2ff0f89e34eb5da993a77392bd',
'de892884aa089f22aced4d19a71b6a1d521c8db6',
'214470ceb026525fa225d52dfe7d27db2c4ddf31',
'e0f4628b4379286c433bab020b9e193fdc437d05',
'be5975cb0b7fcad7ab2c3122a8df3d541befdeab',
'06f7ab60d4914e00b342f098e1ef3e43e501b469',
'e1005bea192673e54faa0c769d9f0fb7439a09b4',
'e22d7f6bf2e1e1969dd963d39ad32c756fb0f20e',
'f4b4b20bb27cbbd1c42eee7b17ce39c5819dc818',
'd717ed969b6a477499eb3cf823c78dbe654ca709',
'6a8d6a42b4c9f4b72caf0a3b808e50909686a2b7',
'afaa9bfa5d26ab419f791d5ca97d602ec52a30a5',
'f553212d9bfbc6e70c0d6a4ac3fe71208bb77ca1',
'c35f8b79f4a5103603ceaaa14d1df3857a166fa1',
'46447341838d480966926d0b32771e281af1c885',
'4e3330081d4e0a3e109cf1cfb514072a90b999d7',
'4199ef93c3184c29362520ea5292d854a8728494',
'a0538938974c60cb9249acf8f0588c3df3c0e4b1',
'49cac0b6c1e0d7d1d04865c328c8cbe5c8e0cda2',
'97e3cc6295515f8292f5f57868506c70446594c3',
'c94e577ef3b0ab48255633c63f0143d9e3eab6f8',
'17d9af726fb6e4a4a8cc4f1becfc11dc9d4db96c',
'4af6ec81ad538e3da23ee88cba65ed8687a72ea3',
'69cb45166d2f4f61069e1f1a975dab3f48bed832',
'ae227082570f75b614bb29593911ada5137654ad',
'882b9fa44962df0b80d9a25b2553796bd4eba2ff',
'e1f5c4c7178c34ce561c493b558df7450995a60c',
'56d7272cd497c73502ec5a09bdf69b4c7ecbfd74',
'36a99b302f7348848ec477ce867a8b78656d9c6c',
'48be7a01565289f44c2c9d4ff1436800c52acf75',
'422cb82e01ee0192256a05217102f45f2f74551d',
'f8744d10460abae3d75e92336b3bb264bb78cc8c',
'5000f246c8c568547c551b11e4b72acd0179e73c',
'52c09522b288766a42900aa73f77216907d16b23',
'61adb59fc097335b45b23fc884a78362b71a3e9b',
'b81c3c58e5a6c45bc8728024068291a7e5f19c1a',
'dec394e5401b62024ed71253e900c25a06eb46f9',
'fbe142d2cb60fb981521cfe72961642d69db8784',
'24a9c4557e0366945c179433fab8434c2c8ee59f',
'ccc28f607ae11fe79c39c42d88798c19b1388af9',
'd4d5ca665e56063fdd37d42d2192c64151915454',
'4273fc07c3e8a36726087becdf331f7d63fc7e66',
'5e0380895cc8e34a6a58f238b7fd33b9e0f027fe',
'146908e48a8a9f92f59fae86a7a4ff28c0ce9109',
'2779a449026423bc0f50a10eead2d9f3720b825c',
'ff9b954908341360ffb39f5eee2857172a29c0e5',
'b00b6d146c4f4af1d28260a3f28abd15f0105221',
'4ffd03df44741d73877a0848935eea0d6cbfc1e9',
'c2c5b73aaae2583cc235319e53200e0e15cda438',
'b7b370faf46f4c682827fbb3967e1ef117b3b0d1',
'bc6c9ac81e5909408518568729c2c3a147402928',
'ba1aa77c5bc5fe545b245f2b1883af3dfeeae6c0',
'97a7060a7e28ab1d13cb2f4ca90be624b417168e',
'3df1da69ffcb4e00cf13c65c9f0497a56413e5e9',
'ebb13b4ed0cb41814839d4084c064e62526b2ec0',
'70b9d50722307f0f45ceed01d421d6a425e95291',
'3f81ca7b73f21ca9b449c147942fbe4fcd31450d',
'86d490939df232966dc6ba363ebc0baaac364701',
'6ba51f3f61025543d66e59c2c1ae3d1b37d5d8d6',
'b728942e1c9a9046c1a5c8d8a25a644d0db7160c',
'920819252a398c0e3da9b6017b110ee047c1748e',
'd7c114d803b1365ea3a975c9600fc8cfd9efb9d4',
'792aa82143bb12e97f396cb2a462ad617dbd22bc',
'b69dda7e6e7c605aacb0ab973a21830c4401db20',
'90b5950051487c57c9120f6d0b98d61dfb05a197',
'177ac767fdfbe9799f91bfc0f22b44109f7b1b20',
'18d71ce8066b0653b5ecd1a235571fd806f420a0',
'39016691752550109065cb506393f9a88eeb5433',
'3477afbf65aac3fd3dfca54770803283d827222a',
'ec17795bcac60741f0b4009fd60e654333173c0d',
'02d64e3e6b65d430adc8f28e76b65fef06112e81',
'6d378d876bb342380e5aaa2671d7c2687bc67df2',
'b47732a57770a5a1780f25bb495cbcc2b61722b9',
'4b666b1f44634a5766220adc84e01c6f804cf552',
'642bbd51338f2ea19e97409e3abaa07391707df9',
'49935920c84157e8c0b981ac6b4eac9f92022613',
'ce4105b9a777ed2b494ee61465584d58faa52cde',
'24aeb8dffb2e9a83161a89e88e9aacf5f01beb63',
'07280e78af25d07f1bd00c391fd0928f5d9aa543',
'13239ea26d46db5a3268592b88ba65f732fcca7a',
'213244f8ec1740c1db65cd02853de2e5736b26bd',
];
static const _commitsFlutter = [
'692b51763d45cc4d574a06cf3d7b9b36f69c5170',
'450478ad313f8291c3a48b00a7331e7d12e2bda0',
'fb75b2b671c7702b549a80a420144097f4fab5a9',
'9020331a243162781b2c5a645cdca892b1d32b97',
'7672bd756ac34099907137e51f276ee5c0cfb582',
'6172ba6c553e7642bc58997c200f5dde488b64bc',
'1da41c5015a28e0025a00b7c189077e3872a358c',
'fafe9c3c1e772019a4894f6c3092f3f633654b1f',
'8d3b8d8b7fa1267439e979803af8d41e237e9c95',
'113ea6ae72deb26444c3b561c65c9c6b2c0f3ef3',
'f4e490131e338bf8b1a3bcab8dae68c56c4498cd',
'b13a8b24362d7a0b2fafe2a404cd9852b27dc688',
'a545549c4e33a548600a8045d1bd5388346cf187',
'f76936bb858dbade620fc8710b5fd89dc1fab750',
'4c4f5e70b14e238ceebd03372765abb204070573',
'3b21872f9aea4d1cdf3a09c7d74c3970d9316330',
'c5643e21933389ab21a45e38515494c53d2cc2f6',
'ee7e28ec979562d0ab722fd24b5e145068ec6b63',
'cbb7a618809ee9dfb525ca6ea28044e016718ae7',
'399a692f7db0bd5faba36003d67880776a3767d3',
'c389cc0b790a10482a82a5ab2915e907aa442ad1',
'd88a5099b202bd289c508d1dd5ef3e006ccf8144',
'c71e0e31aeb6b855477dee84b391d64f14967616',
'88ef36199ddebf36b0596af73047b56ed0208fa3',
'4f5478cce38d837e14b7a032a12500d3fc0f1310',
'02f61b17f1aa91dca2df3108676bc13700f9efe7',
'7ff0723e08fd3f40bf866c164da8f1895ad41e71',
'fbfe04e0c4e7311a92f9407abb6ec6f3c9543d8e',
'dad6f9d4107a5332d15c126207882edbd8acdc97',
'95144d41fa78bde126c4c1460d4f9ec1a0c3daa5',
'1e1df36da3654f3bec977435cee56637ab77d398',
'efb43ece1f40a0a5f04348951eb625f81318587a',
'd2d2a7a0c18d7465dd12225f511d998135e0036d',
'8774a3b33cb281791933f1cd45f0e77ddd9d4bd1',
'3ff83f39d2fe9c228f4fa16709392c27ef1d7d32',
'3f0a7310dfb6ae1ce29c09b58167b2c16cff743a',
'2a4b712361668ae230fbf1077bf6a5ef07552641',
'e6e36963d3bdbd3810f8986ab56811b9ff145a5c',
'9c8defed0245315b2dab8986ae66fc84b186ff3e',
'6d42ae50140884ec72886665cfb5d4bf39f30afe',
'075faff72b964ee4fb6f4e45f260d0223ff55f23',
'1af98a6bfc879852008bd2108242a64c07403b1d',
'5b5a69ff0a4288b1e53c3d1337c5ac69ca26ebfc',
'4c020ad5dad530afde20c2830a5a6f22c6e3b867',
'3109939de842eb6fe00a831e436963d4568bdc0e',
'f38a3e07af777d51606cd9912bc28c274eb6f05b',
'5655697a23d54dadeb4f527424b3da3ffa1051a2',
'9dc7888929d9112dfed9deb16f3f20d6a341bb5b',
'91b2d41a66d1c540233b819525553afc0fa1f58d',
'2d7e80963b3ad3594b7fcab231f1bd08c13cf0d1',
'81c91517d55c15f9f3a10d5b88401c7e191a5179',
'6c9a881a59f6a2e79e0896e0b475ee9293983947',
'8034db0bd38b1c682146474cc5986e2e00dad96f',
'd134331bf8f01c213b87cc611a76a0013ed3e860',
'34073f4f2a86acd23f4966acbb24d550b5f31563',
'56956c33ef102ac0b5fc46b62bd2dd9f50a86616',
'0ef66c7d1a4c3ed303fdd2aa0349d39955b0e0ef',
'935b8f70305b2d82d76b2a15d568d973e2835895',
'9af4145b8ecb5da13d053dd2a6702c73705a862a',
'a8911cbac88ef8f73c083465d0cbc35a7537f35f',
'd33442bedb88e1579c58f5dee14951712bdaf94d',
'7008d4acb2076798da0adf3c16dc08286dbea765',
'06df71c51446e96939c6a615b7c34ce9123806ba',
'230240c56880f2c19bf92d2c32203b064054f173',
'1fdf684e327544e93c39f1da5429c3e880d22d15',
'8a80222e4d09460d6615636df152adc6772cb9f6',
'de1b130cf637ad91ebf17db0893b7e1ce5f2b064',
'e1f11bd5aafda71a21854a0cc53022666c1c5449',
'76f70d21da9c58da773ca7ebb044b2bbb8808c28',
'1f506886e11c3cb59deb216b6139f718f5a2dd67',
'b9e7e3ffe808a93f1e50d3b1729bf1f40e375de2',
'2c24f0f31223fbda063ad40e25219578742ac299',
'e0e7a7d72db3fef30b221433e259e4e94a4d3aec',
'96d292fa0acd9d11e85fd04f7355a47cf3fb2723',
'246b62f221f3966a0fcfcecc3e8cb1ef7ce3e953',
'1e97fd38cf89f94f5159f4e9d0b598a0871103ab',
'6c90a6559480c7b46a3e6c767af614326fb8ca9b',
'2b2bdc6df9e061b0bdf0ff61e9c508d963e3ac64',
'9f2cb479c8aa66c82a4ae9f6ca825aee5949382b',
'c023e5b2474f8ff1c146240dd685237cd8490f89',
'24ce716cfddfef201027c1a5fa2299a8aeffb03e',
'1887f3f8e0d4ebff550dcc08319e338290edf339',
'0c72661d44978a4e9374a14e882bbb26a46c0ac5',
'5e7b2a0bb3e58cbe4771ae3b7965941f9f38b84e',
'64866862f623ceeb45fd8be4782e8db8b58910c0',
'4c7144a4a890b98ed812be8648ff16c917216e8b',
'294aa14e763f7e8a3729f15eef72f50f16b48e05',
'ec11254157f69d5d9260e12f53f82a6569b5c1fa',
'f5825a22a47969ce9fec303dbefee035eadb1acc',
'a58324d980b1e1d6805eac3e4a7748ae5965a242',
'8f61855dede30a55e9e00c9cb0caa62829798cd1',
'7ac16a276391e26e448e50447f2bb4f3e8146c87',
'70870ee8d3b6280e2366f95f067e075f11c08f58',
'ecf688eb21e8e7d6e21456a2d2fbede4c4ab5b94',
'5b1c84cec15ce15283fe67cb202f37d3f9a911be',
'35e8dae34234cf605ab622ae5c91ff0c3676da55',
'6e4a481bdf2793a05a2569693d3b88b200159217',
'9292bb3a66ea6377899df5a7fc17857fa364477c',
'9fa7f81be038464d2aabef4752d2f50ea60ce561',
'aba16bc2db714ba438f5480fd328c14ca92c42db',
];
static const _commitsPackages = [
'8dcfd1186ef968be1398f80432f94bb0a36e6d9e',
'c1f116788a9c0187ac566517eecaf31a49c2bbf7',
'99155a84f372cef1c5fc2d03c54d6980fb9df808',
'b3a69599c7b8d2f97dc7f601df59b09f6a47a8ed',
'43de301d0901cac145b39a994fe3b58889e00b85',
'ca60bd020f339b5538423b3e60463717a702ddc7',
'09ddfca4fe95c5aa90015ce4ecb31485eb7cf3d7',
'94b93d4cd52af75b3298c1966394759a9e611f61',
'071ed5b454e05b071ffb9113c33e7a423ca344b0',
'3c0cdab2bbb0e98e5e5871f5949e38f21cff6385',
'b3280ae0d8ed70ec2e354e000354099b8abcc448',
'afa1a1c3564e5be4795193600b1c2f0e58d80bee',
'2fbf78d1099c2ec04578c0db2737b81632f573da',
'90a2dc1245d7a3e370230bcd2f308a35da67851f',
'70049bdd9b88609b4485cc2cf71ccb60da57031c',
'b2e421bc176c488730567e4edbdee6ed48d00473',
'dd634a2186cfe9e5b57fca2c8cceae7b4fa41790',
'1802599d43eb93a1ada0dfb294d3f030bdacc2a2',
'a9d36fb7b9021b6e980156097fa8c0f8392273f3',
'88afc6863149dfa1a26170a17789a1b711ddbe5a',
'0f2eeaecde3f34322f9e9980177d15beaeb2b871',
'409793bcb784b9464def8698557005fb8851a9e6',
'1ad1a084c057b6c0383f6cf0ba9df70d5d4b0fc6',
'392d771dac6e7956d30fe93ef2595df787f5a287',
'ea9b53ba608a8aa6d0d243832fdcee37928e1614',
'd809b4f2e1cb8381b0328234204c5423a0580999',
'91f7c339b29af157db001e487e14274487f41688',
'86543faf4d5634bdaf5d60661630ddf81ad0617f',
'02f231f376761cc04610e8c566b0ba759db0bda7',
'61b4096307bf8a20786716522a0f5ce55a577d82',
'bba1da378c820040d57cf8cb01ca27c85728fe61',
'14cbff2f7383a1563aef4efd29d92f65d8ddef2a',
'ecace66e92c2f9235e0d811b064d9f0e97f6175e',
'1ea3725c4396e1708571082a635f6132c059fd18',
'a6542ceef278cf10bd82bc0c4c2feca611c582a2',
'44980b66496ec338ed1ed256e40e161c5bb6d09b',
'295819c44093d6fed1fe48c28b3ac01268154387',
'77796111ab5f3d0a63d0f594d9499dc7dcc58c50',
'406a9821eb084426e0264fca36e0afbc3acd5227',
'a643267e6bf368f0ae83a17caf0a369c095ab0df',
'37827fc0a5588f40ea5dba1a70383ae86e8265b6',
'349d8853cab54514b15173337f3203093ccda106',
'ee460d6a01fee815ffbe1dc169f851bd682addd6',
'5bee35271f19d5bd039c5bd460a62e60b426ebca',
'c7170181ffebd5efcc06bf78734c5820a27f7cfd',
'9139f6c6bef51edf08dc587b43b06e359fb5c7de',
'2673dcdf47156efdfae783bb389234df81ac9da7',
'edc45c5f31a12875352048545bed57820e970edd',
'79b53f3424dc889dd0d257c83461644dab46278e',
'1e0338bfd3c6799713fe89c26a55a64521416e16',
'4d0dfb290e773d4bf68d046f797584a172694966',
'27b12509e5203fea3aa2ed3cc4a4c38cf3349634',
'ff15dfdb36837e099424a4590ebf202472029f49',
'e774c2a3f03b2c551506508f813c91d25c297d22',
'fe3de646912443c073773acea83c783be6c2275c',
'bf3a29cfccf105d652b03d2cb681081e6854eddd',
'8d5c5cd0fa83b786429a5c7ce9f93a5f2f132648',
'888ef055975de366031e514317ba8bc39f9c11d5',
'82baf937218655b8befc2a391c955ae8eb7aa674',
'3c04d2df64a497e646d344bf384049c0bcbccdf9',
'03ed07755ce0761fee7ecd8b0a46cf3bfa85c667',
'e5ef6e87e51bd3213e60ae060a48273a5384f855',
'8212bdb4a2c8b5911b3ba33cbf85942917c9dcd9',
'9083bc9bed1d399f54952848c85c54dc6cba8645',
'7293eee6ac127b7c34185a56064f75bf46dcd892',
'173a344964f6426579cb2b9578108d014585c79b',
'12279ffac3cee623e51667e62dd720046f05fa03',
'faa4e22db67cc5e6d6af84ebc85603330fff7353',
'678f033811377ce039a491ad1beae56ac7ba87ed',
'7f9860a70d20bc77166eba1a56f4e9c2d2680db3',
'32a8a23e9be049c0da892e08e36e43cfe76799a1',
'a27d7c50b3ac66cdcc9e774d2a5310793277ed34',
'7026f1a62c30509b40023c67969c5d0eeb7c0f24',
'6301571e051a814d6610ab199484d94f4aa0d795',
'f3ce6cbfa7677fa6ea093982c5c7c4194976d339',
'6d8b19d5fd7ae2f1905885f2b323af1cb2f0ad57',
'3bef3cf3da7603393ed969e5cb1f814322c46058',
'79c529a6bf794cca23ff33299e893a2b118cb19b',
'f234c1f626d56f05eaeb0bd5f1094e5354ed2518',
'e1d01695273f692c6a67848e72bd58b255837f7c',
'f84c6e71882fd6af96fafec34c8fbced81016a0a',
'40bb258f277522f87dc8c01a62293828116bb16a',
'6c20ef3136acf476b3923dc9bdc74a1d512f3849',
'b9ee0b3caffce7a33cac1e72cded5777865c6f79',
'546d3542df2d017142bb9dcb1f142060f319e631',
'7fe183f964b312cc51985067ac54f1ff292236b3',
'd8970b12d60a31e9bae5521142f9881895244f88',
'acd9adbe46762bc305c7502d17527accf184c3a6',
'0b2baeb4fbf3e565f87cb2f7a28394a1d9c0e77d',
'062c8d4f8dd51004a87b0abebb88c1986c5a73a5',
'12013c49611994d08759f056294e347e5b5fa236',
'9fa0fdce48a2b730da20c2d2228b75f819b1cbdd',
'8f2fd365ea7b30762f4e67357b299954f9b4a996',
'76183f418542218f3bb49a7ecfe5e2e1406ed126',
'12b43a192e1f5fa5141181a72d681a15abd003c1',
'df8be18fb5083a0baee0f9612b6488ef7c375d71',
'de38ee1e9da563cbf70436db7f7b2e95595c07d8',
'54b6834b2a74aa1165dd7fba3558edb6e71d1112',
'bf37517977ffaa46c76dfbc1d30e3ef874cd9729',
'673c2ac4b464aacb2b320ffc30eca0ab953e6212',
];
}