Add update packages roller (#100982)
This commit is contained in:
parent
0dd0c2edca
commit
a30012b275
@ -1778,6 +1778,7 @@ const Set<String> kExecutableAllowlist = <String>{
|
|||||||
'dev/bots/docs.sh',
|
'dev/bots/docs.sh',
|
||||||
|
|
||||||
'dev/conductor/bin/conductor',
|
'dev/conductor/bin/conductor',
|
||||||
|
'dev/conductor/bin/roll-packages',
|
||||||
'dev/conductor/core/lib/src/proto/compile_proto.sh',
|
'dev/conductor/core/lib/src/proto/compile_proto.sh',
|
||||||
|
|
||||||
'dev/customer_testing/ci.sh',
|
'dev/customer_testing/ci.sh',
|
||||||
|
@ -40,4 +40,4 @@ DART_BIN="$REPO_DIR/bin/dart"
|
|||||||
# Ensure pub get has been run in the repo before running the conductor
|
# Ensure pub get has been run in the repo before running the conductor
|
||||||
(cd "$REPO_DIR/dev/conductor/core"; "$DART_BIN" pub get 1>&2)
|
(cd "$REPO_DIR/dev/conductor/core"; "$DART_BIN" pub get 1>&2)
|
||||||
|
|
||||||
"$DART_BIN" --enable-asserts "$REPO_DIR/dev/conductor/core/bin/cli.dart" "$@"
|
exec "$DART_BIN" --enable-asserts "$REPO_DIR/dev/conductor/core/bin/cli.dart" "$@"
|
||||||
|
46
dev/conductor/bin/roll-packages
Executable file
46
dev/conductor/bin/roll-packages
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2014 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.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Needed because if it is set, cd may print the path it changed to.
|
||||||
|
unset CDPATH
|
||||||
|
|
||||||
|
# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one
|
||||||
|
# link at a time, and then cds into the link destination and find out where it
|
||||||
|
# ends up.
|
||||||
|
#
|
||||||
|
# The returned filesystem path must be a format usable by Dart's URI parser,
|
||||||
|
# since the Dart command line tool treats its argument as a file URI, not a
|
||||||
|
# filename. For instance, multiple consecutive slashes should be reduced to a
|
||||||
|
# single slash, since double-slashes indicate a URI "authority", and these are
|
||||||
|
# supposed to be filenames. There is an edge case where this will return
|
||||||
|
# multiple slashes: when the input resolves to the root directory. However, if
|
||||||
|
# that were the case, we wouldn't be running this shell, so we don't do anything
|
||||||
|
# about it.
|
||||||
|
#
|
||||||
|
# The function is enclosed in a subshell to avoid changing the working directory
|
||||||
|
# of the caller.
|
||||||
|
function follow_links() (
|
||||||
|
cd -P "$(dirname -- "$1")"
|
||||||
|
file="$PWD/$(basename -- "$1")"
|
||||||
|
while [[ -h "$file" ]]; do
|
||||||
|
cd -P "$(dirname -- "$file")"
|
||||||
|
file="$(readlink -- "$file")"
|
||||||
|
cd -P "$(dirname -- "$file")"
|
||||||
|
file="$PWD/$(basename -- "$file")"
|
||||||
|
done
|
||||||
|
echo "$file"
|
||||||
|
)
|
||||||
|
|
||||||
|
PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")"
|
||||||
|
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
|
||||||
|
REPO_DIR="$BIN_DIR/../../.."
|
||||||
|
DART_BIN="$REPO_DIR/bin/dart"
|
||||||
|
|
||||||
|
# Ensure pub get has been run in the repo before running the conductor
|
||||||
|
(cd "$REPO_DIR/dev/conductor/core"; "$DART_BIN" pub get 1>&2)
|
||||||
|
|
||||||
|
exec "$DART_BIN" --enable-asserts "$REPO_DIR/dev/conductor/core/bin/roll_packages.dart" "$@"
|
128
dev/conductor/core/bin/packages_autoroller.dart
Normal file
128
dev/conductor/core/bin/packages_autoroller.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2014 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:io' as io;
|
||||||
|
|
||||||
|
import 'package:args/args.dart';
|
||||||
|
import 'package:conductor_core/conductor_core.dart';
|
||||||
|
import 'package:conductor_core/packages_autoroller.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
const String kTokenOption = 'token';
|
||||||
|
const String kGithubClient = 'github-client';
|
||||||
|
const String kMirrorRemote = 'mirror-remote';
|
||||||
|
const String kUpstreamRemote = 'upstream-remote';
|
||||||
|
|
||||||
|
Future<void> main(List<String> args) async {
|
||||||
|
final ArgParser parser = ArgParser();
|
||||||
|
parser.addOption(
|
||||||
|
kTokenOption,
|
||||||
|
help: 'GitHub access token env variable name.',
|
||||||
|
defaultsTo: 'GITHUB_TOKEN',
|
||||||
|
);
|
||||||
|
parser.addOption(
|
||||||
|
kGithubClient,
|
||||||
|
help: 'Path to GitHub CLI client. If not provided, it is assumed `gh` is '
|
||||||
|
'present on the PATH.',
|
||||||
|
);
|
||||||
|
parser.addOption(
|
||||||
|
kMirrorRemote,
|
||||||
|
help: 'The mirror git remote that the feature branch will be pushed to. '
|
||||||
|
'Required',
|
||||||
|
mandatory: true,
|
||||||
|
);
|
||||||
|
parser.addOption(
|
||||||
|
kUpstreamRemote,
|
||||||
|
help: 'The upstream git remote that the feature branch will be merged to.',
|
||||||
|
hide: true,
|
||||||
|
defaultsTo: 'https://github.com/flutter/flutter.git',
|
||||||
|
);
|
||||||
|
|
||||||
|
final ArgResults results;
|
||||||
|
try {
|
||||||
|
results = parser.parse(args);
|
||||||
|
} on FormatException {
|
||||||
|
io.stdout.writeln('''
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
${parser.usage}
|
||||||
|
''');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String mirrorUrl = results[kMirrorRemote]! as String;
|
||||||
|
final String upstreamUrl = results[kUpstreamRemote]! as String;
|
||||||
|
const Platform platform = LocalPlatform();
|
||||||
|
final String tokenName = results[kTokenOption]! as String;
|
||||||
|
final String? token = platform.environment[tokenName];
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
throw FormatException(
|
||||||
|
'Tried to read a GitHub access token from env variable \$$tokenName but it was undefined or empty',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final FrameworkRepository framework = FrameworkRepository(
|
||||||
|
_localCheckouts,
|
||||||
|
mirrorRemote: Remote.mirror(mirrorUrl),
|
||||||
|
upstreamRemote: Remote.upstream(upstreamUrl),
|
||||||
|
);
|
||||||
|
|
||||||
|
await PackageAutoroller(
|
||||||
|
framework: framework,
|
||||||
|
githubClient: results[kGithubClient] as String? ?? 'gh',
|
||||||
|
orgName: _parseOrgName(mirrorUrl),
|
||||||
|
token: token,
|
||||||
|
processManager: const LocalProcessManager(),
|
||||||
|
).roll();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _parseOrgName(String remoteUrl) {
|
||||||
|
final RegExp pattern = RegExp(r'^https:\/\/github\.com\/(.*)\/');
|
||||||
|
final RegExpMatch? match = pattern.firstMatch(remoteUrl);
|
||||||
|
if (match == null) {
|
||||||
|
throw FormatException(
|
||||||
|
'Malformed upstream URL "$remoteUrl", should start with "https://github.com/"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return match.group(1)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkouts get _localCheckouts {
|
||||||
|
const FileSystem fileSystem = LocalFileSystem();
|
||||||
|
const ProcessManager processManager = LocalProcessManager();
|
||||||
|
const Platform platform = LocalPlatform();
|
||||||
|
final Stdio stdio = VerboseStdio(
|
||||||
|
stdout: io.stdout,
|
||||||
|
stderr: io.stderr,
|
||||||
|
stdin: io.stdin,
|
||||||
|
);
|
||||||
|
return Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: _localFlutterRoot.parent,
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory get _localFlutterRoot {
|
||||||
|
String filePath;
|
||||||
|
const FileSystem fileSystem = LocalFileSystem();
|
||||||
|
const Platform platform = LocalPlatform();
|
||||||
|
|
||||||
|
filePath = platform.script.toFilePath();
|
||||||
|
final String checkoutsDirname = fileSystem.path.normalize(
|
||||||
|
fileSystem.path.join(
|
||||||
|
fileSystem.path.dirname(filePath), // flutter/dev/conductor/core/bin
|
||||||
|
'..', // flutter/dev/conductor/core
|
||||||
|
'..', // flutter/dev/conductor
|
||||||
|
'..', // flutter/dev
|
||||||
|
'..', // flutter
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return fileSystem.directory(checkoutsDirname);
|
||||||
|
}
|
5
dev/conductor/core/lib/packages_autoroller.dart
Normal file
5
dev/conductor/core/lib/packages_autoroller.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2014 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.
|
||||||
|
|
||||||
|
export 'src/packages_autoroller.dart';
|
208
dev/conductor/core/lib/src/packages_autoroller.dart
Normal file
208
dev/conductor/core/lib/src/packages_autoroller.dart
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Copyright 2014 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:io' as io;
|
||||||
|
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import 'git.dart';
|
||||||
|
import 'globals.dart';
|
||||||
|
import 'repository.dart';
|
||||||
|
|
||||||
|
/// A service for rolling the SDK's pub packages to latest and open a PR upstream.
|
||||||
|
class PackageAutoroller {
|
||||||
|
PackageAutoroller({
|
||||||
|
required this.githubClient,
|
||||||
|
required this.token,
|
||||||
|
required this.framework,
|
||||||
|
required this.orgName,
|
||||||
|
required this.processManager,
|
||||||
|
}) {
|
||||||
|
if (token.trim().isEmpty) {
|
||||||
|
throw Exception('empty token!');
|
||||||
|
}
|
||||||
|
if (githubClient.trim().isEmpty) {
|
||||||
|
throw Exception('Must provide path to GitHub client!');
|
||||||
|
}
|
||||||
|
if (orgName.trim().isEmpty) {
|
||||||
|
throw Exception('Must provide an orgName!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final FrameworkRepository framework;
|
||||||
|
final ProcessManager processManager;
|
||||||
|
|
||||||
|
/// Path to GitHub CLI client.
|
||||||
|
final String githubClient;
|
||||||
|
|
||||||
|
/// GitHub API access token.
|
||||||
|
final String token;
|
||||||
|
|
||||||
|
static const String hostname = 'github.com';
|
||||||
|
|
||||||
|
static const String prBody = '''
|
||||||
|
This PR was generated by `flutter update-packages --force-upgrade`.
|
||||||
|
''';
|
||||||
|
|
||||||
|
/// Name of the feature branch to be opened on against the mirror repo.
|
||||||
|
///
|
||||||
|
/// We never re-use a previous branch, so the branch name ends in an index
|
||||||
|
/// number, which gets incremented for each roll.
|
||||||
|
late final Future<String> featureBranchName = (() async {
|
||||||
|
final List<String> remoteBranches = await framework.listRemoteBranches(framework.mirrorRemote!.name);
|
||||||
|
|
||||||
|
int x = 1;
|
||||||
|
String name(int index) => 'packages-autoroller-branch-$index';
|
||||||
|
|
||||||
|
while (remoteBranches.contains(name(x))) {
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name(x);
|
||||||
|
})();
|
||||||
|
|
||||||
|
/// Name of the GitHub organization to push the feature branch to.
|
||||||
|
final String orgName;
|
||||||
|
|
||||||
|
Future<void> roll() async {
|
||||||
|
await authLogin();
|
||||||
|
await updatePackages();
|
||||||
|
await pushBranch();
|
||||||
|
await createPr(
|
||||||
|
repository: await framework.checkoutDirectory,
|
||||||
|
);
|
||||||
|
await authLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updatePackages({
|
||||||
|
bool verbose = true,
|
||||||
|
String author = 'flutter-packages-autoroller <flutter-packages-autoroller@google.com>'
|
||||||
|
}) async {
|
||||||
|
await framework.newBranch(await featureBranchName);
|
||||||
|
final io.Process flutterProcess = await framework.streamFlutter(<String>[
|
||||||
|
if (verbose) '--verbose',
|
||||||
|
'update-packages',
|
||||||
|
'--force-upgrade',
|
||||||
|
]);
|
||||||
|
final int exitCode = await flutterProcess.exitCode;
|
||||||
|
if (exitCode != 0) {
|
||||||
|
throw ConductorException('Failed to update packages with exit code $exitCode');
|
||||||
|
}
|
||||||
|
await framework.commit(
|
||||||
|
'roll packages',
|
||||||
|
addFirst: true,
|
||||||
|
author: author,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pushBranch() async {
|
||||||
|
await framework.pushRef(
|
||||||
|
fromRef: await featureBranchName,
|
||||||
|
toRef: await featureBranchName,
|
||||||
|
remote: framework.mirrorRemote!.url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> authLogout() {
|
||||||
|
return cli(
|
||||||
|
<String>['auth', 'logout', '--hostname', hostname],
|
||||||
|
allowFailure: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> authLogin() {
|
||||||
|
return cli(
|
||||||
|
<String>[
|
||||||
|
'auth',
|
||||||
|
'login',
|
||||||
|
'--hostname',
|
||||||
|
hostname,
|
||||||
|
'--git-protocol',
|
||||||
|
'https',
|
||||||
|
'--with-token',
|
||||||
|
],
|
||||||
|
stdin: token,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a pull request on GitHub.
|
||||||
|
///
|
||||||
|
/// Depends on the gh cli tool.
|
||||||
|
Future<void> createPr({
|
||||||
|
required io.Directory repository,
|
||||||
|
String title = 'Roll pub packages',
|
||||||
|
String body = 'This PR was generated by `flutter update-packages --force-upgrade`.',
|
||||||
|
String base = FrameworkRepository.defaultBranch,
|
||||||
|
bool draft = false,
|
||||||
|
}) async {
|
||||||
|
// We will wrap title and body in double quotes before delegating to gh
|
||||||
|
// binary
|
||||||
|
await cli(
|
||||||
|
<String>[
|
||||||
|
'pr',
|
||||||
|
'create',
|
||||||
|
'--title',
|
||||||
|
title.trim(),
|
||||||
|
'--body',
|
||||||
|
body.trim(),
|
||||||
|
'--head',
|
||||||
|
'$orgName:${await featureBranchName}',
|
||||||
|
'--base',
|
||||||
|
base,
|
||||||
|
if (draft)
|
||||||
|
'--draft',
|
||||||
|
],
|
||||||
|
workingDirectory: repository.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> help([List<String>? args]) {
|
||||||
|
return cli(<String>[
|
||||||
|
'help',
|
||||||
|
...?args,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> cli(
|
||||||
|
List<String> args, {
|
||||||
|
bool allowFailure = false,
|
||||||
|
String? stdin,
|
||||||
|
String? workingDirectory,
|
||||||
|
}) async {
|
||||||
|
print('Executing "$githubClient ${args.join(' ')}" in $workingDirectory');
|
||||||
|
final io.Process process = await processManager.start(
|
||||||
|
<String>[githubClient, ...args],
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
environment: <String, String>{},
|
||||||
|
);
|
||||||
|
final List<String> stderrStrings = <String>[];
|
||||||
|
final List<String> stdoutStrings = <String>[];
|
||||||
|
final Future<void> stdoutFuture = process.stdout
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.forEach(stdoutStrings.add);
|
||||||
|
final Future<void> stderrFuture = process.stderr
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.forEach(stderrStrings.add);
|
||||||
|
if (stdin != null) {
|
||||||
|
process.stdin.write(stdin);
|
||||||
|
await process.stdin.flush();
|
||||||
|
await process.stdin.close();
|
||||||
|
}
|
||||||
|
final int exitCode = await process.exitCode;
|
||||||
|
await Future.wait(<Future<Object?>>[
|
||||||
|
stdoutFuture,
|
||||||
|
stderrFuture,
|
||||||
|
]);
|
||||||
|
final String stderr = stderrStrings.join();
|
||||||
|
final String stdout = stdoutStrings.join();
|
||||||
|
if (!allowFailure && exitCode != 0) {
|
||||||
|
throw GitException(
|
||||||
|
'$stderr\n$stdout',
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
print(stdout);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:convert' show jsonDecode;
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
@ -30,6 +31,20 @@ class Remote {
|
|||||||
assert(url != null),
|
assert(url != null),
|
||||||
assert(url != '');
|
assert(url != '');
|
||||||
|
|
||||||
|
factory Remote.mirror(String url) {
|
||||||
|
return Remote(
|
||||||
|
name: RemoteName.mirror,
|
||||||
|
url: url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Remote.upstream(String url) {
|
||||||
|
return Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: url,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final RemoteName _name;
|
final RemoteName _name;
|
||||||
|
|
||||||
/// The name of the remote.
|
/// The name of the remote.
|
||||||
@ -134,6 +149,37 @@ abstract class Repository {
|
|||||||
return _checkoutDirectory!;
|
return _checkoutDirectory!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RegExp pattern to parse the output of git ls-remote.
|
||||||
|
///
|
||||||
|
/// Git output looks like:
|
||||||
|
///
|
||||||
|
/// 35185330c6af3a435f615ee8ac2fed8b8bb7d9d4 refs/heads/95159-squash
|
||||||
|
/// 6f60a1e7b2f3d2c2460c9dc20fe54d0e9654b131 refs/heads/add-debug-trace
|
||||||
|
/// c1436c42c0f3f98808ae767e390c3407787f1a67 refs/heads/add-recipe-field
|
||||||
|
/// 4d44dca340603e25d4918c6ef070821181202e69 refs/heads/add-release-channel
|
||||||
|
///
|
||||||
|
/// We are interested in capturing what comes after 'refs/heads/'.
|
||||||
|
static final RegExp _lsRemotePattern = RegExp(r'.*\s+refs\/heads\/([^\s]+)$');
|
||||||
|
|
||||||
|
/// Parse git ls-remote --heads and return branch names.
|
||||||
|
Future<List<String>> listRemoteBranches(String remote) async {
|
||||||
|
final String output = await git.getOutput(
|
||||||
|
<String>['ls-remote', '--heads', remote],
|
||||||
|
'get remote branches',
|
||||||
|
workingDirectory: (await checkoutDirectory).path,
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<String> remoteBranches = <String>[];
|
||||||
|
for (final String line in output.split('\n')) {
|
||||||
|
final RegExpMatch? match = _lsRemotePattern.firstMatch(line);
|
||||||
|
if (match != null) {
|
||||||
|
remoteBranches.add(match.group(1)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteBranches;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensure the repository is cloned to disk and initialized with proper state.
|
/// Ensure the repository is cloned to disk and initialized with proper state.
|
||||||
Future<void> lazilyInitialize(Directory checkoutDirectory) async {
|
Future<void> lazilyInitialize(Directory checkoutDirectory) async {
|
||||||
if (checkoutDirectory.existsSync()) {
|
if (checkoutDirectory.existsSync()) {
|
||||||
@ -408,8 +454,8 @@ abstract class Repository {
|
|||||||
Future<String> commit(
|
Future<String> commit(
|
||||||
String message, {
|
String message, {
|
||||||
bool addFirst = false,
|
bool addFirst = false,
|
||||||
|
String? author,
|
||||||
}) async {
|
}) async {
|
||||||
assert(!message.contains("'"));
|
|
||||||
final bool hasChanges = (await git.getOutput(
|
final bool hasChanges = (await git.getOutput(
|
||||||
<String>['status', '--porcelain'],
|
<String>['status', '--porcelain'],
|
||||||
'check for uncommitted changes',
|
'check for uncommitted changes',
|
||||||
@ -426,8 +472,28 @@ abstract class Repository {
|
|||||||
workingDirectory: (await checkoutDirectory).path,
|
workingDirectory: (await checkoutDirectory).path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
String? authorArg;
|
||||||
|
if (author != null) {
|
||||||
|
if (author.contains('"')) {
|
||||||
|
throw FormatException(
|
||||||
|
'Commit author cannot contain character \'"\', received $author',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// verify [author] matches git author convention, e.g. "Jane Doe <jane.doe@email.com>"
|
||||||
|
if (!RegExp(r'.+<.*>').hasMatch(author)) {
|
||||||
|
throw FormatException(
|
||||||
|
'Commit author appears malformed: "$author"',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
authorArg = '--author="$author"';
|
||||||
|
}
|
||||||
await git.run(
|
await git.run(
|
||||||
<String>['commit', "--message='$message'"],
|
<String>[
|
||||||
|
'commit',
|
||||||
|
'--message',
|
||||||
|
message,
|
||||||
|
if (authorArg != null) authorArg,
|
||||||
|
],
|
||||||
'commit changes',
|
'commit changes',
|
||||||
workingDirectory: (await checkoutDirectory).path,
|
workingDirectory: (await checkoutDirectory).path,
|
||||||
);
|
);
|
||||||
@ -590,6 +656,29 @@ class FrameworkRepository extends Repository {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<io.Process> streamFlutter(
|
||||||
|
List<String> args, {
|
||||||
|
void Function(String)? stdoutCallback,
|
||||||
|
void Function(String)? stderrCallback,
|
||||||
|
}) async {
|
||||||
|
await _ensureToolReady();
|
||||||
|
final io.Process process = await processManager.start(<String>[
|
||||||
|
fileSystem.path.join((await checkoutDirectory).path, 'bin', 'flutter'),
|
||||||
|
...args,
|
||||||
|
]);
|
||||||
|
process
|
||||||
|
.stdout
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.transform(const LineSplitter())
|
||||||
|
.listen(stdoutCallback ?? stdio.printTrace);
|
||||||
|
process
|
||||||
|
.stderr
|
||||||
|
.transform(utf8.decoder)
|
||||||
|
.transform(const LineSplitter())
|
||||||
|
.listen(stderrCallback ?? stdio.printError);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> checkout(String ref) async {
|
Future<void> checkout(String ref) async {
|
||||||
await super.checkout(ref);
|
await super.checkout(ref);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
import 'package:args/args.dart';
|
import 'package:args/args.dart';
|
||||||
import 'package:conductor_core/src/stdio.dart';
|
import 'package:conductor_core/src/stdio.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
@ -31,7 +31,7 @@ void main() {
|
|||||||
const String releaseChannel = 'beta';
|
const String releaseChannel = 'beta';
|
||||||
const String stateFile = '/state-file.json';
|
const String stateFile = '/state-file.json';
|
||||||
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
||||||
final String localOperatingSystem = const LocalPlatform().pathSeparator;
|
final String localOperatingSystem = const LocalPlatform().operatingSystem;
|
||||||
|
|
||||||
group('next command', () {
|
group('next command', () {
|
||||||
late MemoryFileSystem fileSystem;
|
late MemoryFileSystem fileSystem;
|
||||||
@ -502,7 +502,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Create candidate branch version $candidateBranch for $releaseChannel'",
|
'--message',
|
||||||
|
'Create candidate branch version $candidateBranch for $releaseChannel',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -516,7 +517,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
'--message',
|
||||||
|
'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -593,7 +595,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Create candidate branch version $candidateBranch for $releaseChannel'",
|
'--message',
|
||||||
|
'Create candidate branch version $candidateBranch for $releaseChannel',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -607,7 +610,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
'--message',
|
||||||
|
'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -671,7 +675,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Create candidate branch version $candidateBranch for $releaseChannel'",
|
'--message',
|
||||||
|
'Create candidate branch version $candidateBranch for $releaseChannel',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -685,7 +690,8 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
'--message',
|
||||||
|
'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion',
|
||||||
]),
|
]),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
193
dev/conductor/core/test/packages_autoroller_test.dart
Normal file
193
dev/conductor/core/test/packages_autoroller_test.dart
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2014 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' as io;
|
||||||
|
|
||||||
|
import 'package:conductor_core/conductor_core.dart';
|
||||||
|
import 'package:conductor_core/packages_autoroller.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import './common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const String flutterRoot = '/flutter';
|
||||||
|
const String checkoutsParentDirectory = '$flutterRoot/dev/conductor';
|
||||||
|
const String githubClient = 'gh';
|
||||||
|
const String token = '0123456789abcdef';
|
||||||
|
const String orgName = 'flutter-roller';
|
||||||
|
const String mirrorUrl = 'https://githost.com/flutter-roller/flutter.git';
|
||||||
|
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
||||||
|
final String localOperatingSystem = const LocalPlatform().operatingSystem;
|
||||||
|
late MemoryFileSystem fileSystem;
|
||||||
|
late TestStdio stdio;
|
||||||
|
late FrameworkRepository framework;
|
||||||
|
late PackageAutoroller autoroller;
|
||||||
|
late FakeProcessManager processManager;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
stdio = TestStdio();
|
||||||
|
fileSystem = MemoryFileSystem.test();
|
||||||
|
processManager = FakeProcessManager.empty();
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)
|
||||||
|
..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
framework = FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
mirrorRemote: const Remote(
|
||||||
|
name: RemoteName.mirror,
|
||||||
|
url: mirrorUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
autoroller = PackageAutoroller(
|
||||||
|
githubClient: githubClient,
|
||||||
|
token: token,
|
||||||
|
framework: framework,
|
||||||
|
orgName: orgName,
|
||||||
|
processManager: processManager,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can roll with correct inputs', () async {
|
||||||
|
final StreamController<List<int>> controller =
|
||||||
|
StreamController<List<int>>();
|
||||||
|
processManager.addCommands(<FakeCommand>[
|
||||||
|
FakeCommand(command: const <String>[
|
||||||
|
'gh',
|
||||||
|
'auth',
|
||||||
|
'login',
|
||||||
|
'--hostname',
|
||||||
|
'github.com',
|
||||||
|
'--git-protocol',
|
||||||
|
'https',
|
||||||
|
'--with-token',
|
||||||
|
], stdin: io.IOSink(controller.sink)),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
|
'--',
|
||||||
|
FrameworkRepository.defaultUpstream,
|
||||||
|
'$checkoutsParentDirectory/flutter_conductor_checkouts/framework',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'remote',
|
||||||
|
'add',
|
||||||
|
'mirror',
|
||||||
|
mirrorUrl,
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'fetch',
|
||||||
|
'mirror',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'checkout',
|
||||||
|
FrameworkRepository.defaultBranch,
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: 'deadbeef'),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'ls-remote',
|
||||||
|
'--heads',
|
||||||
|
'mirror',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'checkout',
|
||||||
|
'-b',
|
||||||
|
'packages-autoroller-branch-1',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'$checkoutsParentDirectory/flutter_conductor_checkouts/framework/bin/flutter',
|
||||||
|
'help',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'$checkoutsParentDirectory/flutter_conductor_checkouts/framework/bin/flutter',
|
||||||
|
'--verbose',
|
||||||
|
'update-packages',
|
||||||
|
'--force-upgrade',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'status',
|
||||||
|
'--porcelain',
|
||||||
|
], stdout: '''
|
||||||
|
M packages/foo/pubspec.yaml
|
||||||
|
M packages/bar/pubspec.yaml
|
||||||
|
M dev/integration_tests/test_foo/pubspec.yaml
|
||||||
|
'''),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'add',
|
||||||
|
'--all',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'commit',
|
||||||
|
'--message',
|
||||||
|
'roll packages',
|
||||||
|
'--author="flutter-packages-autoroller <flutter-packages-autoroller@google.com>"',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: '000deadbeef'),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'push',
|
||||||
|
mirrorUrl,
|
||||||
|
'packages-autoroller-branch-1:packages-autoroller-branch-1',
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'gh',
|
||||||
|
'pr',
|
||||||
|
'create',
|
||||||
|
'--title',
|
||||||
|
'Roll pub packages',
|
||||||
|
'--body',
|
||||||
|
'This PR was generated by `flutter update-packages --force-upgrade`.',
|
||||||
|
'--head',
|
||||||
|
'flutter-roller:packages-autoroller-branch-1',
|
||||||
|
'--base',
|
||||||
|
FrameworkRepository.defaultBranch,
|
||||||
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'gh',
|
||||||
|
'auth',
|
||||||
|
'logout',
|
||||||
|
'--hostname',
|
||||||
|
'github.com',
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
final Future<void> rollFuture = autoroller.roll();
|
||||||
|
final String givenToken =
|
||||||
|
await controller.stream.transform(const Utf8Decoder()).join();
|
||||||
|
expect(givenToken, token);
|
||||||
|
await rollFuture;
|
||||||
|
});
|
||||||
|
}
|
@ -13,6 +13,7 @@ void main() {
|
|||||||
group('repository', () {
|
group('repository', () {
|
||||||
late FakePlatform platform;
|
late FakePlatform platform;
|
||||||
const String rootDir = '/';
|
const String rootDir = '/';
|
||||||
|
const String revision = 'deadbeef';
|
||||||
late MemoryFileSystem fileSystem;
|
late MemoryFileSystem fileSystem;
|
||||||
late FakeProcessManager processManager;
|
late FakeProcessManager processManager;
|
||||||
late TestStdio stdio;
|
late TestStdio stdio;
|
||||||
@ -31,8 +32,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('canCherryPick returns true if git cherry-pick returns 0', () async {
|
test('canCherryPick returns true if git cherry-pick returns 0', () async {
|
||||||
const String commit = 'abc123';
|
|
||||||
|
|
||||||
processManager.addCommands(<FakeCommand>[
|
processManager.addCommands(<FakeCommand>[
|
||||||
FakeCommand(command: <String>[
|
FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
@ -53,7 +52,7 @@ void main() {
|
|||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
'HEAD',
|
'HEAD',
|
||||||
], stdout: commit),
|
], stdout: revision),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'status',
|
'status',
|
||||||
@ -63,7 +62,7 @@ void main() {
|
|||||||
'git',
|
'git',
|
||||||
'cherry-pick',
|
'cherry-pick',
|
||||||
'--no-commit',
|
'--no-commit',
|
||||||
commit,
|
revision,
|
||||||
]),
|
]),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
@ -80,7 +79,7 @@ void main() {
|
|||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
);
|
);
|
||||||
final Repository repository = FrameworkRepository(checkouts);
|
final Repository repository = FrameworkRepository(checkouts);
|
||||||
expect(await repository.canCherryPick(commit), true);
|
expect(await repository.canCherryPick(revision), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('canCherryPick returns false if git cherry-pick returns non-zero', () async {
|
test('canCherryPick returns false if git cherry-pick returns non-zero', () async {
|
||||||
@ -262,7 +261,8 @@ vars = {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='$message'",
|
'--message',
|
||||||
|
message,
|
||||||
]),
|
]),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
@ -318,7 +318,8 @@ vars = {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'commit',
|
'commit',
|
||||||
"--message='$message'",
|
'--message',
|
||||||
|
message,
|
||||||
]),
|
]),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
@ -501,6 +502,66 @@ vars = {
|
|||||||
expect(processManager.hasRemainingExpectations, false);
|
expect(processManager.hasRemainingExpectations, false);
|
||||||
expect(createdCandidateBranch, true);
|
expect(createdCandidateBranch, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('.listRemoteBranches() parses git output', () async {
|
||||||
|
const String remoteName = 'mirror';
|
||||||
|
const String lsRemoteOutput = '''
|
||||||
|
Extraneous debug information that should be ignored.
|
||||||
|
|
||||||
|
4d44dca340603e25d4918c6ef070821181202e69 refs/heads/experiment
|
||||||
|
35185330c6af3a435f615ee8ac2fed8b8bb7d9d4 refs/heads/feature-a
|
||||||
|
6f60a1e7b2f3d2c2460c9dc20fe54d0e9654b131 refs/heads/feature-b
|
||||||
|
c1436c42c0f3f98808ae767e390c3407787f1a67 refs/heads/fix_bug_1234
|
||||||
|
bbbcae73699263764ad4421a4b2ca3952a6f96cb refs/heads/stable
|
||||||
|
|
||||||
|
Extraneous debug information that should be ignored.
|
||||||
|
''';
|
||||||
|
processManager.addCommands(const <FakeCommand>[
|
||||||
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
|
'--',
|
||||||
|
EngineRepository.defaultUpstream,
|
||||||
|
'${rootDir}flutter_conductor_checkouts/engine',
|
||||||
|
]),
|
||||||
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'checkout',
|
||||||
|
'main',
|
||||||
|
]),
|
||||||
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: revision),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'ls-remote', '--heads', remoteName],
|
||||||
|
stdout: lsRemoteOutput,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(rootDir),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
|
||||||
|
final Repository repo = EngineRepository(
|
||||||
|
checkouts,
|
||||||
|
localUpstream: true,
|
||||||
|
);
|
||||||
|
final List<String> branchNames = await repo.listRemoteBranches(remoteName);
|
||||||
|
expect(branchNames, equals(<String>[
|
||||||
|
'experiment',
|
||||||
|
'feature-a',
|
||||||
|
'feature-b',
|
||||||
|
'fix_bug_1234',
|
||||||
|
'stable',
|
||||||
|
]));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ void main() {
|
|||||||
command: <String>['git', 'add', '--all'],
|
command: <String>['git', 'add', '--all'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
|
command: <String>['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -400,7 +400,7 @@ void main() {
|
|||||||
command: <String>['git', 'add', '--all'],
|
command: <String>['git', 'add', '--all'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
|
command: <String>['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -574,7 +574,7 @@ void main() {
|
|||||||
command: <String>['git', 'add', '--all'],
|
command: <String>['git', 'add', '--all'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
|
command: <String>['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -761,7 +761,7 @@ void main() {
|
|||||||
command: <String>['git', 'add', '--all'],
|
command: <String>['git', 'add', '--all'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
|
command: <String>['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
@ -952,7 +952,7 @@ void main() {
|
|||||||
command: <String>['git', 'add', '--all'],
|
command: <String>['git', 'add', '--all'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
|
command: <String>['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'],
|
||||||
),
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user