Unverified Commit 1f1065d5 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_conductor] fix pushing local working branch to mirror remote (#88057)

parent c91a2982
...@@ -182,19 +182,25 @@ String getNewPrLink({ ...@@ -182,19 +182,25 @@ String getNewPrLink({
if (repoName == 'engine') { if (repoName == 'engine') {
if (state.engine.dartRevision.isNotEmpty) { if (state.engine.dartRevision.isNotEmpty) {
// shorten hashes to make final link manageable // shorten hashes to make final link manageable
// prefix with github org/repo so GitHub will auto-generate a hyperlink
body.writeln('- Roll dart revision: dart-lang/sdk@${state.engine.dartRevision.substring(0, 9)}'); body.writeln('- Roll dart revision: dart-lang/sdk@${state.engine.dartRevision.substring(0, 9)}');
} }
body.writeAll( for (final pb.Cherrypick cp in state.engine.cherrypicks) {
state.engine.cherrypicks.map<String>((pb.Cherrypick cp) => '- commit: ${cp.trunkRevision.substring(0, 9)}'), // Only list commits that map to a commit that exists upstream.
'\n', if (cp.trunkRevision.isNotEmpty) {
); body.writeln('- commit: flutter/engine@${cp.trunkRevision.substring(0, 9)}');
}
}
} else { } else {
body.writeAll( for (final pb.Cherrypick cp in state.framework.cherrypicks) {
state.framework.cherrypicks.map<String>((pb.Cherrypick cp) => '- commit: ${cp.trunkRevision.substring(0, 9)}'), // Only list commits that map to a commit that exists upstream.
'\n', if (cp.trunkRevision.isNotEmpty) {
); body.writeln('- commit: ${cp.trunkRevision.substring(0, 9)}');
}
}
} }
return 'https://github.com/flutter/$repoName/compare/$candidateBranch...$userName:$workingBranch?' return 'https://github.com/flutter/$repoName/compare/'
'$candidateBranch...$userName:$workingBranch?'
'expand=1' 'expand=1'
'&title=${Uri.encodeQueryComponent(title)}' '&title=${Uri.encodeQueryComponent(title)}'
'&body=${Uri.encodeQueryComponent(body.toString())}'; '&body=${Uri.encodeQueryComponent(body.toString())}';
......
...@@ -95,11 +95,31 @@ void runNext({ ...@@ -95,11 +95,31 @@ void runNext({
switch (state.currentPhase) { switch (state.currentPhase) {
case pb.ReleasePhase.APPLY_ENGINE_CHERRYPICKS: case pb.ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[]; final Remote upstream = Remote(
for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) { name: RemoteName.upstream,
if (!finishedStates.contains(cherrypick.state)) { url: state.engine.upstream.url,
unappliedCherrypicks.add(cherrypick); );
} final EngineRepository engine = EngineRepository(
checkouts,
initialRef: state.engine.workingBranch,
upstreamRemote: upstream,
previousCheckoutLocation: state.engine.checkoutPath,
);
final String headRevision = engine.reverseParse('HEAD');
// check if the candidate branch is enabled in .ci.yaml
if (!engine.ciYaml.enabledBranches.contains(state.engine.candidateBranch)) {
engine.ciYaml.enableBranch(state.engine.candidateBranch);
// commit
final String revision = engine.commit(
'add branch ${state.engine.candidateBranch} to enabled_branches in .ci.yaml',
addFirst: true,
);
// append to list of cherrypicks so we know a PR is required
state.engine.cherrypicks.add(pb.Cherrypick(
appliedRevision: revision,
state: pb.CherrypickState.COMPLETED,
));
} }
if (!requiresEnginePR(state)) { if (!requiresEnginePR(state)) {
...@@ -109,6 +129,13 @@ void runNext({ ...@@ -109,6 +129,13 @@ void runNext({
break; break;
} }
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) {
if (!finishedStates.contains(cherrypick.state)) {
unappliedCherrypicks.add(cherrypick);
}
}
if (unappliedCherrypicks.isEmpty) { if (unappliedCherrypicks.isEmpty) {
stdio.printStatus('All engine cherrypicks have been auto-applied by the conductor.\n'); stdio.printStatus('All engine cherrypicks have been auto-applied by the conductor.\n');
} else { } else {
...@@ -128,21 +155,11 @@ void runNext({ ...@@ -128,21 +155,11 @@ void runNext({
return; return;
} }
} }
final Remote upstream = Remote(
name: RemoteName.upstream,
url: state.engine.upstream.url,
);
final EngineRepository engine = EngineRepository(
checkouts,
initialRef: state.engine.workingBranch,
upstreamRemote: upstream,
previousCheckoutLocation: state.engine.checkoutPath,
);
final String headRevision = engine.reverseParse('HEAD');
engine.pushRef( engine.pushRef(
fromRef: headRevision, fromRef: headRevision,
toRef: state.engine.workingBranch, // Explicitly create new branch
toRef: 'refs/heads/${state.engine.workingBranch}',
remote: state.engine.mirror.name, remote: state.engine.mirror.name,
); );
...@@ -166,13 +183,6 @@ void runNext({ ...@@ -166,13 +183,6 @@ void runNext({
} }
break; break;
case pb.ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: case pb.ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) {
if (!finishedStates.contains(cherrypick.state)) {
unappliedCherrypicks.add(cherrypick);
}
}
if (state.engine.cherrypicks.isEmpty && state.engine.dartRevision.isEmpty) { if (state.engine.cherrypicks.isEmpty && state.engine.dartRevision.isEmpty) {
stdio.printStatus( stdio.printStatus(
'This release has no engine cherrypicks, and thus the engine.version file\n' 'This release has no engine cherrypicks, and thus the engine.version file\n'
...@@ -213,11 +223,41 @@ void runNext({ ...@@ -213,11 +223,41 @@ void runNext({
); );
final String headRevision = framework.reverseParse('HEAD'); final String headRevision = framework.reverseParse('HEAD');
// Check if the current candidate branch is enabled
if (!framework.ciYaml.enabledBranches.contains(state.framework.candidateBranch)) {
framework.ciYaml.enableBranch(state.framework.candidateBranch);
// commit
final String revision = framework.commit(
'add branch ${state.framework.candidateBranch} to enabled_branches in .ci.yaml',
addFirst: true,
);
// append to list of cherrypicks so we know a PR is required
state.framework.cherrypicks.add(pb.Cherrypick(
appliedRevision: revision,
state: pb.CherrypickState.COMPLETED,
));
}
stdio.printStatus('Rolling new engine hash $engineRevision to framework checkout...'); stdio.printStatus('Rolling new engine hash $engineRevision to framework checkout...');
framework.updateEngineRevision(engineRevision); final bool needsCommit = framework.updateEngineRevision(engineRevision);
framework.commit( if (needsCommit) {
final String revision = framework.commit(
'Update Engine revision to $engineRevision for ${state.releaseChannel} release ${state.releaseVersion}', 'Update Engine revision to $engineRevision for ${state.releaseChannel} release ${state.releaseVersion}',
addFirst: true); addFirst: true,
);
// append to list of cherrypicks so we know a PR is required
state.framework.cherrypicks.add(pb.Cherrypick(
appliedRevision: revision,
state: pb.CherrypickState.COMPLETED,
));
}
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) {
if (!finishedStates.contains(cherrypick.state)) {
unappliedCherrypicks.add(cherrypick);
}
}
if (state.framework.cherrypicks.isEmpty) { if (state.framework.cherrypicks.isEmpty) {
stdio.printStatus( stdio.printStatus(
...@@ -251,7 +291,8 @@ void runNext({ ...@@ -251,7 +291,8 @@ void runNext({
framework.pushRef( framework.pushRef(
fromRef: headRevision, fromRef: headRevision,
toRef: state.framework.workingBranch, // Explicitly create new branch
toRef: 'refs/heads/${state.framework.workingBranch}',
remote: state.framework.mirror.name, remote: state.framework.mirror.name,
); );
break; break;
......
...@@ -9,6 +9,7 @@ import 'package:file/file.dart'; ...@@ -9,6 +9,7 @@ import 'package:file/file.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:yaml/yaml.dart';
import './git.dart'; import './git.dart';
import './globals.dart'; import './globals.dart';
...@@ -69,6 +70,7 @@ abstract class Repository { ...@@ -69,6 +70,7 @@ abstract class Repository {
'Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!'); 'Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!');
} }
if (initialRef != null) { if (initialRef != null) {
assert(initialRef != '');
git.run( git.run(
<String>['fetch', upstreamRemote.name], <String>['fetch', upstreamRemote.name],
'Fetch ${upstreamRemote.name} to ensure we have latest refs', 'Fetch ${upstreamRemote.name} to ensure we have latest refs',
...@@ -398,6 +400,14 @@ abstract class Repository { ...@@ -398,6 +400,14 @@ abstract class Repository {
bool addFirst = false, bool addFirst = false,
}) { }) {
assert(!message.contains("'")); assert(!message.contains("'"));
final bool hasChanges = git.getOutput(
<String>['status', '--porcelain'],
'check for uncommitted changes',
workingDirectory: checkoutDirectory.path,
).trim().isNotEmpty;
if (!hasChanges) {
throw ConductorException('Tried to commit with message $message but no changes were present');
}
if (addFirst) { if (addFirst) {
git.run( git.run(
<String>['add', '--all'], <String>['add', '--all'],
...@@ -490,6 +500,7 @@ class FrameworkRepository extends Repository { ...@@ -490,6 +500,7 @@ class FrameworkRepository extends Repository {
} }
final Checkouts checkouts; final Checkouts checkouts;
late final CiYaml ciYaml = CiYaml(checkoutDirectory.childFile('.ci.yaml'));
static const String defaultUpstream = static const String defaultUpstream =
'https://github.com/flutter/flutter.git'; 'https://github.com/flutter/flutter.git';
...@@ -586,7 +597,10 @@ class FrameworkRepository extends Repository { ...@@ -586,7 +597,10 @@ class FrameworkRepository extends Repository {
return Version.fromString(versionJson['frameworkVersion'] as String); return Version.fromString(versionJson['frameworkVersion'] as String);
} }
void updateEngineRevision( /// Update this framework's engine version file.
///
/// Returns [true] if the version file was updated and a commit is needed.
bool updateEngineRevision(
String newEngine, { String newEngine, {
@visibleForTesting File? engineVersionFile, @visibleForTesting File? engineVersionFile,
}) { }) {
...@@ -597,8 +611,19 @@ class FrameworkRepository extends Repository { ...@@ -597,8 +611,19 @@ class FrameworkRepository extends Repository {
.childFile('engine.version'); .childFile('engine.version');
assert(engineVersionFile.existsSync()); assert(engineVersionFile.existsSync());
final String oldEngine = engineVersionFile.readAsStringSync(); final String oldEngine = engineVersionFile.readAsStringSync();
if (oldEngine.trim() == newEngine.trim()) {
stdio.printTrace(
'Tried to update the engine revision but version file is already up to date at: $newEngine',
);
return false;
}
stdio.printStatus('Updating engine revision from $oldEngine to $newEngine'); stdio.printStatus('Updating engine revision from $oldEngine to $newEngine');
engineVersionFile.writeAsStringSync(newEngine.trim(), flush: true); engineVersionFile.writeAsStringSync(
// Version files have trailing newlines
'${newEngine.trim()}\n',
flush: true,
);
return true;
} }
} }
...@@ -699,6 +724,8 @@ class EngineRepository extends Repository { ...@@ -699,6 +724,8 @@ class EngineRepository extends Repository {
final Checkouts checkouts; final Checkouts checkouts;
late final CiYaml ciYaml = CiYaml(checkoutDirectory.childFile('.ci.yaml'));
static const String defaultUpstream = 'https://github.com/flutter/engine.git'; static const String defaultUpstream = 'https://github.com/flutter/engine.git';
static const String defaultBranch = 'master'; static const String defaultBranch = 'master';
...@@ -766,3 +793,68 @@ class Checkouts { ...@@ -766,3 +793,68 @@ class Checkouts {
final ProcessManager processManager; final ProcessManager processManager;
final Stdio stdio; final Stdio stdio;
} }
class CiYaml {
CiYaml(this.file) {
if (!file.existsSync()) {
throw ConductorException('Could not find the .ci.yaml file at ${file.path}');
}
}
/// Underlying [File] that this object wraps.
final File file;
/// Returns the raw string contents of this file.
///
/// This is not cached as the contents can be written to while the conductor
/// is running.
String get stringContents => file.readAsStringSync();
/// Returns the parsed contents of the file as a [YamlMap].
///
/// This is not cached as the contents can be written to while the conductor
/// is running.
YamlMap get contents => loadYaml(stringContents) as YamlMap;
List<String> get enabledBranches {
final YamlList yamlList = contents['enabled_branches'] as YamlList;
return yamlList.map<String>((dynamic element) {
return element as String;
}).toList();
}
static final RegExp _enabledBranchPattern = RegExp(r'^enabled_branches:');
/// Update this .ci.yaml file with the given branch name.
///
/// The underlying [File] is written to, but not committed to git. This method
/// will throw a [ConductorException] if the [branchName] is already present
/// in the file or if the file does not have an "enabled_branches:" field.
void enableBranch(String branchName) {
final List<String> newStrings = <String>[];
if (enabledBranches.contains(branchName)) {
throw ConductorException('${file.path} already contains the branch $branchName');
}
if (!_enabledBranchPattern.hasMatch(stringContents)) {
throw ConductorException(
'Did not find the expected string "enabled_branches:" in the file ${file.path}',
);
}
final List<String> lines = stringContents.split('\n');
bool insertedCurrentBranch = false;
for (final String line in lines) {
// Every existing line should be copied to the new Yaml
newStrings.add(line);
if (insertedCurrentBranch) {
continue;
}
if (_enabledBranchPattern.hasMatch(line)) {
insertedCurrentBranch = true;
// Indent two spaces
final String indent = ' ' * 2;
newStrings.add('$indent- ${branchName.trim()}');
}
}
file.writeAsStringSync(newStrings.join('\n'), flush: true);
}
}
...@@ -267,8 +267,7 @@ bool requiresEnginePR(pb.ConductorState state) { ...@@ -267,8 +267,7 @@ bool requiresEnginePR(pb.ConductorState state) {
/// This release will require a new Framework PR. /// This release will require a new Framework PR.
/// ///
/// The logic is if there was an Engine PR OR there are framework cherrypicks /// The logic is if there was an Engine PR OR there are framework cherrypicks
/// that have not been abandoned OR the increment level is 'm', then return /// that have not been abandoned.
/// true, else false.
bool requiresFrameworkPR(pb.ConductorState state) { bool requiresFrameworkPR(pb.ConductorState state) {
if (requiresEnginePR(state)) { if (requiresEnginePR(state)) {
return true; return true;
...@@ -278,9 +277,5 @@ bool requiresFrameworkPR(pb.ConductorState state) { ...@@ -278,9 +277,5 @@ bool requiresFrameworkPR(pb.ConductorState state) {
if (hasRequiredCherrypicks) { if (hasRequiredCherrypicks) {
return true; return true;
} }
if (state.incrementLevel == 'm') {
// requires an update to .ci.yaml
return true;
}
return false; return false;
} }
...@@ -89,8 +89,9 @@ void main() { ...@@ -89,8 +89,9 @@ void main() {
## Scheduled Cherrypicks ## Scheduled Cherrypicks
- Roll dart revision: dart-lang/sdk@${dartRevision.substring(0, 9)} - Roll dart revision: dart-lang/sdk@${dartRevision.substring(0, 9)}
- commit: ${engineCherrypick1.substring(0, 9)} - commit: flutter/engine@${engineCherrypick1.substring(0, 9)}
- commit: ${engineCherrypick2.substring(0, 9)}'''; - commit: flutter/engine@${engineCherrypick2.substring(0, 9)}
''';
expect( expect(
Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''), Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''),
expectedBody, expectedBody,
...@@ -120,7 +121,8 @@ void main() { ...@@ -120,7 +121,8 @@ void main() {
## Scheduled Cherrypicks ## Scheduled Cherrypicks
- commit: ${frameworkCherrypick.substring(0, 9)}'''; - commit: ${frameworkCherrypick.substring(0, 9)}
''';
expect( expect(
Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''), Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''),
expectedBody, expectedBody,
......
This diff is collapsed.
...@@ -224,6 +224,65 @@ vars = { ...@@ -224,6 +224,65 @@ vars = {
); );
}); });
test('commit() throws if there are no local changes to commit', () {
const String commit1 = 'abc123';
const String commit2 = 'def456';
const String message = 'This is a commit message.';
final TestStdio stdio = TestStdio();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
EngineRepository.defaultUpstream,
fileSystem.path
.join(rootDir, 'flutter_conductor_checkouts', 'engine'),
]),
const FakeCommand(command: <String>[
'git',
'checkout',
'upstream/master',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit1),
const FakeCommand(command: <String>[
'git',
'status',
'--porcelain',
]),
const FakeCommand(command: <String>[
'git',
'commit',
"--message='$message'",
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit2),
]);
final Checkouts checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: fileSystem.directory(rootDir),
platform: platform,
processManager: processManager,
stdio: stdio,
);
final EngineRepository repo = EngineRepository(checkouts);
expect(
() => repo.commit(message),
throwsExceptionWith('Tried to commit with message $message but no changes were present'),
);
});
test('commit() passes correct commit message', () { test('commit() passes correct commit message', () {
const String commit1 = 'abc123'; const String commit1 = 'abc123';
const String commit2 = 'def456'; const String commit2 = 'def456';
...@@ -251,6 +310,10 @@ vars = { ...@@ -251,6 +310,10 @@ vars = {
'rev-parse', 'rev-parse',
'HEAD', 'HEAD',
], stdout: commit1), ], stdout: commit1),
const FakeCommand(
command: <String>['git', 'status', '--porcelain'],
stdout: 'MM path/to/file.txt',
),
const FakeCommand(command: <String>[ const FakeCommand(command: <String>[
'git', 'git',
'commit', 'commit',
...@@ -274,6 +337,167 @@ vars = { ...@@ -274,6 +337,167 @@ vars = {
final EngineRepository repo = EngineRepository(checkouts); final EngineRepository repo = EngineRepository(checkouts);
repo.commit(message); repo.commit(message);
}); });
test('updateEngineRevision() returns false if newCommit is the same as version file', () {
const String commit1 = 'abc123';
const String commit2 = 'def456';
final TestStdio stdio = TestStdio();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final File engineVersionFile = fileSystem.file('/engine.version')..writeAsStringSync(commit2);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
fileSystem.path
.join(rootDir, 'flutter_conductor_checkouts', 'framework'),
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit1),
]);
final Checkouts checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: fileSystem.directory(rootDir),
platform: platform,
processManager: processManager,
stdio: stdio,
);
final FrameworkRepository repo = FrameworkRepository(checkouts);
final bool didUpdate = repo.updateEngineRevision(commit2, engineVersionFile: engineVersionFile);
expect(didUpdate, false);
});
test('CiYaml(file) will throw if file does not exist', () {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final File file = fileSystem.file('/non/existent/file.txt');
expect(
() => CiYaml(file),
throwsExceptionWith('Could not find the .ci.yaml file at /non/existent/file.txt'),
);
});
test('ciYaml.enableBranch() will prepend the given branch to the yaml list of enabled_branches', () {
const String commit1 = 'abc123';
final TestStdio stdio = TestStdio();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final File ciYaml = fileSystem.file('/flutter_conductor_checkouts/framework/.ci.yaml');
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
fileSystem.path
.join(rootDir, 'flutter_conductor_checkouts', 'framework'),
],
onRun: () {
ciYaml.createSync(recursive: true);
ciYaml.writeAsStringSync('''
enabled_branches:
- master
- dev
- beta
- stable
''');
}),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit1),
]);
final Checkouts checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: fileSystem.directory(rootDir),
platform: platform,
processManager: processManager,
stdio: stdio,
);
final FrameworkRepository framework = FrameworkRepository(checkouts);
expect(
framework.ciYaml.enabledBranches,
<String>['master', 'dev', 'beta', 'stable'],
);
framework.ciYaml.enableBranch('foo');
expect(
framework.ciYaml.enabledBranches,
<String>['foo', 'master', 'dev', 'beta', 'stable'],
);
expect(
framework.ciYaml.stringContents,
'''
enabled_branches:
- foo
- master
- dev
- beta
- stable
'''
);
});
test('ciYaml.enableBranch() will throw if the input branch is already present in the yaml file', () {
const String commit1 = 'abc123';
final TestStdio stdio = TestStdio();
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final File ciYaml = fileSystem.file('/flutter_conductor_checkouts/framework/.ci.yaml');
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
FakeCommand(
command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
FrameworkRepository.defaultUpstream,
fileSystem.path
.join(rootDir, 'flutter_conductor_checkouts', 'framework'),
],
onRun: () {
ciYaml.createSync(recursive: true);
ciYaml.writeAsStringSync('''
enabled_branches:
- master
- dev
- beta
- stable
''');
}),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit1),
]);
final Checkouts checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: fileSystem.directory(rootDir),
platform: platform,
processManager: processManager,
stdio: stdio,
);
final FrameworkRepository framework = FrameworkRepository(checkouts);
expect(
() => framework.ciYaml.enableBranch('master'),
throwsExceptionWith('.ci.yaml already contains the branch master'),
);
});
}); });
} }
......
...@@ -161,6 +161,10 @@ void main() { ...@@ -161,6 +161,10 @@ void main() {
'cherrypicks-$candidateBranch', 'cherrypicks-$candidateBranch',
], ],
), ),
const FakeCommand(
command: <String>['git', 'status', '--porcelain'],
stdout: 'MM path/to/DEPS',
),
const FakeCommand( const FakeCommand(
command: <String>['git', 'add', '--all'], command: <String>['git', 'add', '--all'],
), ),
...@@ -272,6 +276,7 @@ void main() { ...@@ -272,6 +276,7 @@ void main() {
jsonDecode(stateFile.readAsStringSync()), jsonDecode(stateFile.readAsStringSync()),
); );
expect(processManager.hasRemainingExpectations, false);
expect(state.isInitialized(), true); expect(state.isInitialized(), true);
expect(state.releaseChannel, releaseChannel); expect(state.releaseChannel, releaseChannel);
expect(state.releaseVersion, nextVersion); expect(state.releaseVersion, nextVersion);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment