blob: 2278848be1ccdfdc342684e2e8a4af49cf972bc2 [file] [log] [blame]
// Copyright 2020 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:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:appengine/appengine.dart';
import 'package:cocoon_service/src/service/github_service.dart';
import 'package:github/github.dart';
import '../datastore/cocoon_config.dart';
import '../foundation/typedefs.dart';
/// Signature for a function that calculates the backoff duration to wait in
/// between requests when GitHub responds with an error.
///
/// The [attempt] argument is zero-based, so if the first attempt to request
/// from GitHub fails, and we're backing off before making the second attempt,
/// the [attempt] argument will be zero.
typedef GitHubBackoffCalculator = Duration Function(int attempt);
/// Default backoff calculator.
Duration twoSecondLinearBackoff(int attempt) {
return const Duration(seconds: 2) * (attempt + 1);
}
Future<List<String>> loadBranchRegExps(
HttpClientProvider branchHttpClientProvider,
Logging log,
GitHubBackoffCalculator gitHubBackoffCalculator) async {
const String path = '/flutter/cocoon/master/app_dart/dev/branch_regexps.txt';
final Uri url = Uri.https('raw.githubusercontent.com', path);
final HttpClient client = branchHttpClientProvider();
try {
// TODO(keyonghan): apply retry logic here to simply, https://github.com/flutter/flutter/issues/52427
for (int attempt = 0; attempt < 3; attempt++) {
final HttpClientRequest clientRequest = await client.getUrl(url);
try {
final HttpClientResponse clientResponse = await clientRequest.close();
final int status = clientResponse.statusCode;
if (status == HttpStatus.ok) {
final String content = await utf8.decoder.bind(clientResponse).join();
final List<String> branches = content
.split('\n')
.map((String branch) => branch.trim())
.toList();
branches.removeWhere((String branch) => branch.isEmpty);
return branches;
} else {
log.warning(
'Attempt to download branch_regexps.txt failed (HTTP $status)');
}
} catch (error, stackTrace) {
log.error(
'Attempt to download branch_regexps.txt failed:\n$error\n$stackTrace');
}
await Future<void>.delayed(gitHubBackoffCalculator(attempt));
}
} finally {
client.close(force: true);
}
log.error('GitHub not responding; giving up');
return <String>['master'];
}
Future<Uint8List> getBranches(
Config config,
HttpClientProvider branchHttpClientProvider,
Logging log,
GitHubBackoffCalculator gitHubBackoffCalculator) async {
final GithubService githubService = await config.createGithubService();
final GitHub github = githubService.github;
final RepositorySlug slug = RepositorySlug('flutter', 'flutter');
final Stream<Branch> branchList = github.repositories.listBranches(slug);
final List<String> regExps = await loadBranchRegExps(
branchHttpClientProvider, log, gitHubBackoffCalculator);
final List<Branch> branches = <Branch>[];
await for (Branch branch in branchList) {
if (!regExps.any((String regExp) => RegExp(regExp).hasMatch(branch.name))) {
continue;
}
branches.add(branch);
}
return Uint8List.fromList(branches
.map((Branch branch) => branch.name)
.toList()
.join(',')
.codeUnits);
}