mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_conductor] support pushing local changes to remote (#85797)
This commit is contained in:
parent
e9736efb75
commit
1aad8c8c22
@ -104,29 +104,22 @@ void runNext({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.engine.cherrypicks.isEmpty) {
|
if (state.engine.cherrypicks.isEmpty && state.engine.dartRevision.isEmpty) {
|
||||||
stdio.printStatus('This release has no engine cherrypicks.');
|
stdio.printStatus(
|
||||||
|
'This release has no engine cherrypicks. No Engine PR is necessary.\n',
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
} else if (unappliedCherrypicks.isEmpty) {
|
}
|
||||||
|
|
||||||
|
if (unappliedCherrypicks.isEmpty) {
|
||||||
stdio.printStatus('All engine cherrypicks have been auto-applied by '
|
stdio.printStatus('All engine cherrypicks have been auto-applied by '
|
||||||
'the conductor.\n');
|
'the conductor.\n');
|
||||||
if (autoAccept == false) {
|
|
||||||
final bool response = prompt(
|
|
||||||
'Are you ready to push your changes to the repository '
|
|
||||||
'${state.engine.mirror.url}?',
|
|
||||||
stdio,
|
|
||||||
);
|
|
||||||
if (!response) {
|
|
||||||
stdio.printError('Aborting command.');
|
|
||||||
writeStateToFile(stateFile, state, stdio.logs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
stdio.printStatus(
|
stdio.printStatus(
|
||||||
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.');
|
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.');
|
||||||
stdio.printStatus('These must be applied manually in the directory '
|
stdio.printStatus('These must be applied manually in the directory '
|
||||||
'${state.engine.checkoutPath} before proceeding.\n');
|
'${state.engine.checkoutPath} before proceeding.\n');
|
||||||
|
}
|
||||||
if (autoAccept == false) {
|
if (autoAccept == false) {
|
||||||
final bool response = prompt(
|
final bool response = prompt(
|
||||||
'Are you ready to push your engine branch to the repository '
|
'Are you ready to push your engine branch to the repository '
|
||||||
@ -139,9 +132,30 @@ 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(
|
||||||
|
fromRef: headRevision,
|
||||||
|
toRef: state.engine.workingBranch,
|
||||||
|
remote: state.engine.mirror.name,
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case pb.ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
case pb.ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
||||||
|
stdio.printStatus(<String>[
|
||||||
|
'You must validate pre-submit CI for your engine PR, merge it, and codesign',
|
||||||
|
'binaries before proceeding.\n',
|
||||||
|
].join('\n'));
|
||||||
if (autoAccept == false) {
|
if (autoAccept == false) {
|
||||||
// TODO(fujino): actually test if binaries have been codesigned on macOS
|
// TODO(fujino): actually test if binaries have been codesigned on macOS
|
||||||
final bool response = prompt(
|
final bool response = prompt(
|
||||||
@ -163,29 +177,65 @@ void runNext({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.framework.cherrypicks.isEmpty) {
|
if (state.engine.cherrypicks.isEmpty && state.engine.dartRevision.isEmpty) {
|
||||||
stdio.printStatus('This release has no framework cherrypicks.');
|
stdio.printStatus(
|
||||||
break;
|
'This release has no engine cherrypicks, and thus the engine.version file\n'
|
||||||
} else if (unappliedCherrypicks.isEmpty) {
|
'in the framework does not need to be updated.',
|
||||||
stdio.printStatus('All framework cherrypicks have been auto-applied by '
|
|
||||||
'the conductor.\n');
|
|
||||||
if (autoAccept == false) {
|
|
||||||
final bool response = prompt(
|
|
||||||
'Are you ready to push your changes to the repository '
|
|
||||||
'${state.framework.mirror.url}?',
|
|
||||||
stdio,
|
|
||||||
);
|
);
|
||||||
if (!response) {
|
|
||||||
stdio.printError('Aborting command.');
|
if (state.framework.cherrypicks.isEmpty) {
|
||||||
writeStateToFile(stateFile, state, stdio.logs);
|
stdio.printStatus(
|
||||||
return;
|
'This release also has no framework cherrypicks. Therefore, a framework\n'
|
||||||
|
'pull request is not required.',
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final EngineRepository engine = EngineRepository(
|
||||||
|
checkouts,
|
||||||
|
initialRef: state.engine.candidateBranch,
|
||||||
|
upstreamRemote: Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: state.engine.upstream.url,
|
||||||
|
),
|
||||||
|
previousCheckoutLocation: state.engine.checkoutPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
final String engineRevision = engine.reverseParse('HEAD');
|
||||||
|
|
||||||
|
final Remote upstream = Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: state.framework.upstream.url,
|
||||||
|
);
|
||||||
|
final FrameworkRepository framework = FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
initialRef: state.framework.workingBranch,
|
||||||
|
upstreamRemote: upstream,
|
||||||
|
previousCheckoutLocation: state.framework.checkoutPath,
|
||||||
|
);
|
||||||
|
final String headRevision = framework.reverseParse('HEAD');
|
||||||
|
|
||||||
|
stdio.printStatus('Rolling new engine hash $engineRevision to framework checkout...');
|
||||||
|
framework.updateEngineRevision(engineRevision);
|
||||||
|
framework.commit('Update Engine revision to $engineRevision for ${state.releaseChannel} release ${state.releaseVersion}', addFirst: true);
|
||||||
|
|
||||||
|
if (state.framework.cherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus(
|
||||||
|
'This release has no framework cherrypicks. However, a framework PR is still\n'
|
||||||
|
'required to roll engine cherrypicks.',
|
||||||
|
);
|
||||||
|
} else if (unappliedCherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus('All framework cherrypicks were auto-applied by the conductor.');
|
||||||
} else {
|
} else {
|
||||||
stdio.printStatus(
|
stdio.printStatus(
|
||||||
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.');
|
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.',
|
||||||
stdio.printStatus('These must be applied manually in the directory '
|
);
|
||||||
'${state.framework.checkoutPath} before proceeding.\n');
|
stdio.printStatus(
|
||||||
|
'These must be applied manually in the directory '
|
||||||
|
'${state.framework.checkoutPath} before proceeding.\n',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (autoAccept == false) {
|
if (autoAccept == false) {
|
||||||
final bool response = prompt(
|
final bool response = prompt(
|
||||||
'Are you ready to push your framework branch to the repository '
|
'Are you ready to push your framework branch to the repository '
|
||||||
@ -198,7 +248,12 @@ void runNext({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
framework.pushRef(
|
||||||
|
fromRef: headRevision,
|
||||||
|
toRef: state.framework.workingBranch,
|
||||||
|
remote: state.framework.mirror.name,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case pb.ReleasePhase.PUBLISH_VERSION:
|
case pb.ReleasePhase.PUBLISH_VERSION:
|
||||||
stdio.printStatus('Please ensure that you have merged your framework PR and that');
|
stdio.printStatus('Please ensure that you have merged your framework PR and that');
|
||||||
@ -216,7 +271,8 @@ void runNext({
|
|||||||
final String headRevision = framework.reverseParse('HEAD');
|
final String headRevision = framework.reverseParse('HEAD');
|
||||||
if (autoAccept == false) {
|
if (autoAccept == false) {
|
||||||
final bool response = prompt(
|
final bool response = prompt(
|
||||||
'Has CI passed for the framework PR?',
|
'Are you ready to tag commit $headRevision as ${state.releaseVersion}\n'
|
||||||
|
'and push to remote ${state.framework.upstream.url}?',
|
||||||
stdio,
|
stdio,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
@ -240,9 +296,17 @@ void runNext({
|
|||||||
);
|
);
|
||||||
final String headRevision = framework.reverseParse('HEAD');
|
final String headRevision = framework.reverseParse('HEAD');
|
||||||
if (autoAccept == false) {
|
if (autoAccept == false) {
|
||||||
|
// dryRun: true means print out git command
|
||||||
|
framework.pushRef(
|
||||||
|
fromRef: headRevision,
|
||||||
|
toRef: state.releaseChannel,
|
||||||
|
remote: state.framework.upstream.url,
|
||||||
|
force: force,
|
||||||
|
dryRun: true,
|
||||||
|
);
|
||||||
|
|
||||||
final bool response = prompt(
|
final bool response = prompt(
|
||||||
'Are you ready to publish release ${state.releaseVersion} to '
|
'Are you ready to publish this release?',
|
||||||
'channel ${state.releaseChannel} at ${state.framework.upstream.url}?',
|
|
||||||
stdio,
|
stdio,
|
||||||
);
|
);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
@ -251,10 +315,10 @@ void runNext({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
framework.updateChannel(
|
framework.pushRef(
|
||||||
headRevision,
|
fromRef: headRevision,
|
||||||
state.framework.upstream.url,
|
toRef: state.releaseChannel,
|
||||||
state.releaseChannel,
|
remote: state.framework.upstream.url,
|
||||||
force: force,
|
force: force,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
@ -204,6 +204,8 @@ class Repository extends $pb.GeneratedMessage {
|
|||||||
subBuilder: Cherrypick.create)
|
subBuilder: Cherrypick.create)
|
||||||
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dartRevision',
|
..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dartRevision',
|
||||||
protoName: 'dartRevision')
|
protoName: 'dartRevision')
|
||||||
|
..aOS(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'workingBranch',
|
||||||
|
protoName: 'workingBranch')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
Repository._() : super();
|
Repository._() : super();
|
||||||
@ -216,6 +218,7 @@ class Repository extends $pb.GeneratedMessage {
|
|||||||
Remote mirror,
|
Remote mirror,
|
||||||
$core.Iterable<Cherrypick> cherrypicks,
|
$core.Iterable<Cherrypick> cherrypicks,
|
||||||
$core.String dartRevision,
|
$core.String dartRevision,
|
||||||
|
$core.String workingBranch,
|
||||||
}) {
|
}) {
|
||||||
final _result = create();
|
final _result = create();
|
||||||
if (candidateBranch != null) {
|
if (candidateBranch != null) {
|
||||||
@ -242,6 +245,9 @@ class Repository extends $pb.GeneratedMessage {
|
|||||||
if (dartRevision != null) {
|
if (dartRevision != null) {
|
||||||
_result.dartRevision = dartRevision;
|
_result.dartRevision = dartRevision;
|
||||||
}
|
}
|
||||||
|
if (workingBranch != null) {
|
||||||
|
_result.workingBranch = workingBranch;
|
||||||
|
}
|
||||||
return _result;
|
return _result;
|
||||||
}
|
}
|
||||||
factory Repository.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
factory Repository.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
@ -356,6 +362,18 @@ class Repository extends $pb.GeneratedMessage {
|
|||||||
$core.bool hasDartRevision() => $_has(7);
|
$core.bool hasDartRevision() => $_has(7);
|
||||||
@$pb.TagNumber(8)
|
@$pb.TagNumber(8)
|
||||||
void clearDartRevision() => clearField(8);
|
void clearDartRevision() => clearField(8);
|
||||||
|
|
||||||
|
@$pb.TagNumber(9)
|
||||||
|
$core.String get workingBranch => $_getSZ(8);
|
||||||
|
@$pb.TagNumber(9)
|
||||||
|
set workingBranch($core.String v) {
|
||||||
|
$_setString(8, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
@$pb.TagNumber(9)
|
||||||
|
$core.bool hasWorkingBranch() => $_has(8);
|
||||||
|
@$pb.TagNumber(9)
|
||||||
|
void clearWorkingBranch() => clearField(9);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConductorState extends $pb.GeneratedMessage {
|
class ConductorState extends $pb.GeneratedMessage {
|
||||||
|
@ -81,12 +81,13 @@ const Repository$json = const {
|
|||||||
const {'1': 'mirror', '3': 6, '4': 1, '5': 11, '6': '.conductor_state.Remote', '10': 'mirror'},
|
const {'1': 'mirror', '3': 6, '4': 1, '5': 11, '6': '.conductor_state.Remote', '10': 'mirror'},
|
||||||
const {'1': 'cherrypicks', '3': 7, '4': 3, '5': 11, '6': '.conductor_state.Cherrypick', '10': 'cherrypicks'},
|
const {'1': 'cherrypicks', '3': 7, '4': 3, '5': 11, '6': '.conductor_state.Cherrypick', '10': 'cherrypicks'},
|
||||||
const {'1': 'dartRevision', '3': 8, '4': 1, '5': 9, '10': 'dartRevision'},
|
const {'1': 'dartRevision', '3': 8, '4': 1, '5': 9, '10': 'dartRevision'},
|
||||||
|
const {'1': 'workingBranch', '3': 9, '4': 1, '5': 9, '10': 'workingBranch'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `Repository`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `Repository`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List repositoryDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List repositoryDescriptor = $convert.base64Decode(
|
||||||
'CgpSZXBvc2l0b3J5EigKD2NhbmRpZGF0ZUJyYW5jaBgBIAEoCVIPY2FuZGlkYXRlQnJhbmNoEigKD3N0YXJ0aW5nR2l0SGVhZBgCIAEoCVIPc3RhcnRpbmdHaXRIZWFkEiYKDmN1cnJlbnRHaXRIZWFkGAMgASgJUg5jdXJyZW50R2l0SGVhZBIiCgxjaGVja291dFBhdGgYBCABKAlSDGNoZWNrb3V0UGF0aBIzCgh1cHN0cmVhbRgFIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSCHVwc3RyZWFtEi8KBm1pcnJvchgGIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSBm1pcnJvchI9CgtjaGVycnlwaWNrcxgHIAMoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5DaGVycnlwaWNrUgtjaGVycnlwaWNrcxIiCgxkYXJ0UmV2aXNpb24YCCABKAlSDGRhcnRSZXZpc2lvbg==');
|
'CgpSZXBvc2l0b3J5EigKD2NhbmRpZGF0ZUJyYW5jaBgBIAEoCVIPY2FuZGlkYXRlQnJhbmNoEigKD3N0YXJ0aW5nR2l0SGVhZBgCIAEoCVIPc3RhcnRpbmdHaXRIZWFkEiYKDmN1cnJlbnRHaXRIZWFkGAMgASgJUg5jdXJyZW50R2l0SGVhZBIiCgxjaGVja291dFBhdGgYBCABKAlSDGNoZWNrb3V0UGF0aBIzCgh1cHN0cmVhbRgFIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSCHVwc3RyZWFtEi8KBm1pcnJvchgGIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSBm1pcnJvchI9CgtjaGVycnlwaWNrcxgHIAMoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5DaGVycnlwaWNrUgtjaGVycnlwaWNrcxIiCgxkYXJ0UmV2aXNpb24YCCABKAlSDGRhcnRSZXZpc2lvbhIkCg13b3JraW5nQnJhbmNoGAkgASgJUg13b3JraW5nQnJhbmNo');
|
||||||
@$core.Deprecated('Use conductorStateDescriptor instead')
|
@$core.Deprecated('Use conductorStateDescriptor instead')
|
||||||
const ConductorState$json = const {
|
const ConductorState$json = const {
|
||||||
'1': 'ConductorState',
|
'1': 'ConductorState',
|
||||||
|
@ -84,6 +84,12 @@ message Repository {
|
|||||||
|
|
||||||
// Only for engine repositories.
|
// Only for engine repositories.
|
||||||
string dartRevision = 8;
|
string dartRevision = 8;
|
||||||
|
|
||||||
|
// Name of local and remote branch for applying cherrypicks.
|
||||||
|
//
|
||||||
|
// When the pull request is merged, all commits here will be squashed to a
|
||||||
|
// single commit on the [candidateBranch].
|
||||||
|
string workingBranch = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ConductorState {
|
message ConductorState {
|
||||||
|
@ -25,7 +25,9 @@ class Remote {
|
|||||||
const Remote({
|
const Remote({
|
||||||
required RemoteName name,
|
required RemoteName name,
|
||||||
required this.url,
|
required this.url,
|
||||||
}) : _name = name, assert(url != null), assert (url != '');
|
}) : _name = name,
|
||||||
|
assert(url != null),
|
||||||
|
assert(url != '');
|
||||||
|
|
||||||
final RemoteName _name;
|
final RemoteName _name;
|
||||||
|
|
||||||
@ -63,9 +65,15 @@ abstract class Repository {
|
|||||||
if (previousCheckoutLocation != null) {
|
if (previousCheckoutLocation != null) {
|
||||||
_checkoutDirectory = fileSystem.directory(previousCheckoutLocation);
|
_checkoutDirectory = fileSystem.directory(previousCheckoutLocation);
|
||||||
if (!_checkoutDirectory!.existsSync()) {
|
if (!_checkoutDirectory!.existsSync()) {
|
||||||
throw ConductorException('Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!');
|
throw ConductorException(
|
||||||
|
'Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!');
|
||||||
}
|
}
|
||||||
if (initialRef != null) {
|
if (initialRef != null) {
|
||||||
|
git.run(
|
||||||
|
<String>['fetch', upstreamRemote.name],
|
||||||
|
'Fetch ${upstreamRemote.name} to ensure we have latest refs',
|
||||||
|
workingDirectory: _checkoutDirectory!.path,
|
||||||
|
);
|
||||||
git.run(
|
git.run(
|
||||||
<String>['checkout', '${upstreamRemote.name}/$initialRef'],
|
<String>['checkout', '${upstreamRemote.name}/$initialRef'],
|
||||||
'Checking out initialRef $initialRef',
|
'Checking out initialRef $initialRef',
|
||||||
@ -255,11 +263,9 @@ abstract class Repository {
|
|||||||
/// List commits in reverse chronological order.
|
/// List commits in reverse chronological order.
|
||||||
List<String> revList(List<String> args) {
|
List<String> revList(List<String> args) {
|
||||||
return git
|
return git
|
||||||
.getOutput(
|
.getOutput(<String>['rev-list', ...args],
|
||||||
<String>['rev-list', ...args],
|
|
||||||
'rev-list with args ${args.join(' ')}',
|
'rev-list with args ${args.join(' ')}',
|
||||||
workingDirectory: checkoutDirectory.path
|
workingDirectory: checkoutDirectory.path)
|
||||||
)
|
|
||||||
.trim()
|
.trim()
|
||||||
.split('\n');
|
.split('\n');
|
||||||
}
|
}
|
||||||
@ -356,22 +362,33 @@ abstract class Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push [commit] to the release channel [branch].
|
/// Push [commit] to the release channel [branch].
|
||||||
void updateChannel(
|
void pushRef({
|
||||||
String commit,
|
required String fromRef,
|
||||||
String remote,
|
required String remote,
|
||||||
String branch, {
|
required String toRef,
|
||||||
bool force = false,
|
bool force = false,
|
||||||
|
bool dryRun = false,
|
||||||
}) {
|
}) {
|
||||||
git.run(
|
final List<String> args = <String>[
|
||||||
<String>[
|
|
||||||
'push',
|
'push',
|
||||||
if (force) '--force',
|
if (force) '--force',
|
||||||
remote,
|
remote,
|
||||||
'$commit:$branch',
|
'$fromRef:$toRef',
|
||||||
],
|
];
|
||||||
|
final String command = <String>[
|
||||||
|
'git',
|
||||||
|
...args,
|
||||||
|
].join(' ');
|
||||||
|
if (dryRun) {
|
||||||
|
stdio.printStatus('About to execute command: `$command`');
|
||||||
|
} else {
|
||||||
|
git.run(
|
||||||
|
args,
|
||||||
'update the release branch with the commit',
|
'update the release branch with the commit',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
|
stdio.printStatus('Executed command: `$command`');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String commit(
|
String commit(
|
||||||
@ -566,6 +583,21 @@ class FrameworkRepository extends Repository {
|
|||||||
) as Map<String, dynamic>;
|
) as Map<String, dynamic>;
|
||||||
return Version.fromString(versionJson['frameworkVersion'] as String);
|
return Version.fromString(versionJson['frameworkVersion'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateEngineRevision(
|
||||||
|
String newEngine, {
|
||||||
|
@visibleForTesting File? engineVersionFile,
|
||||||
|
}) {
|
||||||
|
assert(newEngine.isNotEmpty);
|
||||||
|
engineVersionFile ??= checkoutDirectory
|
||||||
|
.childDirectory('bin')
|
||||||
|
.childDirectory('internal')
|
||||||
|
.childFile('engine.version');
|
||||||
|
assert(engineVersionFile.existsSync());
|
||||||
|
final String oldEngine = engineVersionFile.readAsStringSync();
|
||||||
|
stdio.printStatus('Updating engine revision from $oldEngine to $newEngine');
|
||||||
|
engineVersionFile.writeAsStringSync(newEngine.trim(), flush: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around the host repository that is executing the conductor.
|
/// A wrapper around the host repository that is executing the conductor.
|
||||||
@ -594,17 +626,20 @@ class HostFrameworkRepository extends FrameworkRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void newBranch(String branchName) {
|
void newBranch(String branchName) {
|
||||||
throw ConductorException('newBranch not implemented for the host repository');
|
throw ConductorException(
|
||||||
|
'newBranch not implemented for the host repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void checkout(String ref) {
|
void checkout(String ref) {
|
||||||
throw ConductorException('checkout not implemented for the host repository');
|
throw ConductorException(
|
||||||
|
'checkout not implemented for the host repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String cherryPick(String commit) {
|
String cherryPick(String commit) {
|
||||||
throw ConductorException('cherryPick not implemented for the host repository');
|
throw ConductorException(
|
||||||
|
'cherryPick not implemented for the host repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -617,14 +652,15 @@ class HostFrameworkRepository extends FrameworkRepository {
|
|||||||
throw ConductorException('tag not implemented for the host repository');
|
throw ConductorException('tag not implemented for the host repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void updateChannel(
|
void updateChannel(
|
||||||
String commit,
|
String commit,
|
||||||
String remote,
|
String remote,
|
||||||
String branch, {
|
String branch, {
|
||||||
bool force = false,
|
bool force = false,
|
||||||
|
bool dryRun = false,
|
||||||
}) {
|
}) {
|
||||||
throw ConductorException('updateChannel not implemented for the host repository');
|
throw ConductorException(
|
||||||
|
'updateChannel not implemented for the host repository');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -673,20 +709,20 @@ class EngineRepository extends Repository {
|
|||||||
depsFile ??= checkoutDirectory.childFile('DEPS');
|
depsFile ??= checkoutDirectory.childFile('DEPS');
|
||||||
final String fileContent = depsFile.readAsStringSync();
|
final String fileContent = depsFile.readAsStringSync();
|
||||||
final RegExp dartPattern = RegExp("[ ]+'dart_revision': '([a-z0-9]{40})',");
|
final RegExp dartPattern = RegExp("[ ]+'dart_revision': '([a-z0-9]{40})',");
|
||||||
final Iterable<RegExpMatch> allMatches = dartPattern.allMatches(fileContent);
|
final Iterable<RegExpMatch> allMatches =
|
||||||
|
dartPattern.allMatches(fileContent);
|
||||||
if (allMatches.length != 1) {
|
if (allMatches.length != 1) {
|
||||||
throw ConductorException(
|
throw ConductorException(
|
||||||
'Unexpected content in the DEPS file at ${depsFile.path}\n'
|
'Unexpected content in the DEPS file at ${depsFile.path}\n'
|
||||||
'Expected to find pattern ${dartPattern.pattern} 1 times, but got '
|
'Expected to find pattern ${dartPattern.pattern} 1 times, but got '
|
||||||
'${allMatches.length}.'
|
'${allMatches.length}.');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
final String updatedFileContent = fileContent.replaceFirst(
|
final String updatedFileContent = fileContent.replaceFirst(
|
||||||
dartPattern,
|
dartPattern,
|
||||||
" 'dart_revision': '$newRevision',",
|
" 'dart_revision': '$newRevision',",
|
||||||
);
|
);
|
||||||
|
|
||||||
depsFile.writeAsStringSync(updatedFileContent);
|
depsFile.writeAsStringSync(updatedFileContent, flush: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -191,10 +191,10 @@ bool rollDev({
|
|||||||
repository.tag(commit, version.toString(), remoteName);
|
repository.tag(commit, version.toString(), remoteName);
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.updateChannel(
|
repository.pushRef(
|
||||||
commit,
|
fromRef: commit,
|
||||||
remoteName,
|
remote: remoteName,
|
||||||
'dev',
|
toRef: 'dev',
|
||||||
force: force,
|
force: force,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ class StartCommand extends Command<void> {
|
|||||||
'y': 'Indicates the first dev release after a beta release.',
|
'y': 'Indicates the first dev release after a beta release.',
|
||||||
'z': 'Indicates a hotfix to a stable release.',
|
'z': 'Indicates a hotfix to a stable release.',
|
||||||
'm': 'Indicates a standard dev release.',
|
'm': 'Indicates a standard dev release.',
|
||||||
'n': 'Indicates a hotfix to a dev release.',
|
'n': 'Indicates a hotfix to a dev or beta release.',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final Git git = Git(processManager);
|
final Git git = Git(processManager);
|
||||||
@ -230,7 +230,8 @@ class StartCommand extends Command<void> {
|
|||||||
|
|
||||||
// Create a new branch so that we don't accidentally push to upstream
|
// Create a new branch so that we don't accidentally push to upstream
|
||||||
// candidateBranch.
|
// candidateBranch.
|
||||||
engine.newBranch('cherrypicks-$candidateBranch');
|
final String workingBranchName = 'cherrypicks-$candidateBranch';
|
||||||
|
engine.newBranch(workingBranchName);
|
||||||
|
|
||||||
if (dartRevision != null && dartRevision.isNotEmpty) {
|
if (dartRevision != null && dartRevision.isNotEmpty) {
|
||||||
engine.updateDartRevision(dartRevision);
|
engine.updateDartRevision(dartRevision);
|
||||||
@ -262,6 +263,7 @@ class StartCommand extends Command<void> {
|
|||||||
final String engineHead = engine.reverseParse('HEAD');
|
final String engineHead = engine.reverseParse('HEAD');
|
||||||
state.engine = pb.Repository(
|
state.engine = pb.Repository(
|
||||||
candidateBranch: candidateBranch,
|
candidateBranch: candidateBranch,
|
||||||
|
workingBranch: workingBranchName,
|
||||||
startingGitHead: engineHead,
|
startingGitHead: engineHead,
|
||||||
currentGitHead: engineHead,
|
currentGitHead: engineHead,
|
||||||
checkoutPath: engine.checkoutDirectory.path,
|
checkoutPath: engine.checkoutDirectory.path,
|
||||||
@ -282,7 +284,7 @@ class StartCommand extends Command<void> {
|
|||||||
url: frameworkMirror,
|
url: frameworkMirror,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
framework.newBranch('cherrypicks-$candidateBranch');
|
framework.newBranch(workingBranchName);
|
||||||
final List<pb.Cherrypick> frameworkCherrypicks = _sortCherrypicks(
|
final List<pb.Cherrypick> frameworkCherrypicks = _sortCherrypicks(
|
||||||
repository: framework,
|
repository: framework,
|
||||||
cherrypicks: frameworkCherrypickRevisions,
|
cherrypicks: frameworkCherrypickRevisions,
|
||||||
@ -320,6 +322,7 @@ class StartCommand extends Command<void> {
|
|||||||
final String frameworkHead = framework.reverseParse('HEAD');
|
final String frameworkHead = framework.reverseParse('HEAD');
|
||||||
state.framework = pb.Repository(
|
state.framework = pb.Repository(
|
||||||
candidateBranch: candidateBranch,
|
candidateBranch: candidateBranch,
|
||||||
|
workingBranch: workingBranchName,
|
||||||
startingGitHead: frameworkHead,
|
startingGitHead: frameworkHead,
|
||||||
currentGitHead: frameworkHead,
|
currentGitHead: frameworkHead,
|
||||||
checkoutPath: framework.checkoutDirectory.path,
|
checkoutPath: framework.checkoutDirectory.path,
|
||||||
|
@ -21,7 +21,7 @@ String luciConsoleLink(String channel, String groupName) {
|
|||||||
'channel $channel not recognized',
|
'channel $channel not recognized',
|
||||||
);
|
);
|
||||||
assert(
|
assert(
|
||||||
<String>['framework', 'engine', 'devicelab'].contains(groupName),
|
<String>['framework', 'engine', 'devicelab', 'packaging'].contains(groupName),
|
||||||
'group named $groupName not recognized',
|
'group named $groupName not recognized',
|
||||||
);
|
);
|
||||||
final String consoleName = channel == 'master' ? groupName : '${channel}_$groupName';
|
final String consoleName = channel == 'master' ? groupName : '${channel}_$groupName';
|
||||||
@ -133,8 +133,9 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
||||||
return <String>[
|
return <String>[
|
||||||
'You must verify Engine CI builds are successful and then codesign the',
|
'You must verify pre-submit CI builds on your engine pull request are successful,',
|
||||||
'binaries at revision ${state.engine.currentGitHead}.',
|
'merge your pull request, validate post-submit CI, and then codesign the binaries ',
|
||||||
|
'on the merge commit.',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
||||||
final List<pb.Cherrypick> outstandingCherrypicks = state.framework.cherrypicks.where(
|
final List<pb.Cherrypick> outstandingCherrypicks = state.framework.cherrypicks.where(
|
||||||
@ -150,13 +151,14 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.PUBLISH_VERSION:
|
case ReleasePhase.PUBLISH_VERSION:
|
||||||
return <String>[
|
return <String>[
|
||||||
'You must verify Framework CI builds are successful.',
|
'You must verify pre-submit CI builds on your framework pull request are successful,',
|
||||||
'See $kReleaseDocumentationUrl for more information.',
|
'merge your pull request, and validate post-submit CI. See $kReleaseDocumentationUrl,',
|
||||||
|
'for more information.',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.PUBLISH_CHANNEL:
|
case ReleasePhase.PUBLISH_CHANNEL:
|
||||||
return 'Issue `conductor next` to publish your release to the release branch.';
|
return 'Issue `conductor next` to publish your release to the release branch.';
|
||||||
case ReleasePhase.VERIFY_RELEASE:
|
case ReleasePhase.VERIFY_RELEASE:
|
||||||
return 'Release archive packages must be verified on cloud storage.';
|
return 'Release archive packages must be verified on cloud storage: ${luciConsoleLink(state.releaseChannel, 'packaging')}';
|
||||||
case ReleasePhase.RELEASE_COMPLETED:
|
case ReleasePhase.RELEASE_COMPLETED:
|
||||||
return 'This release has been completed.';
|
return 'This release has been completed.';
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:conductor/proto/conductor_state.pb.dart' as pb;
|
|||||||
import 'package:conductor/proto/conductor_state.pbenum.dart' show ReleasePhase;
|
import 'package:conductor/proto/conductor_state.pbenum.dart' show ReleasePhase;
|
||||||
import 'package:conductor/repository.dart';
|
import 'package:conductor/repository.dart';
|
||||||
import 'package:conductor/state.dart';
|
import 'package:conductor/state.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
@ -20,11 +21,16 @@ import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
group('next command', () {
|
group('next command', () {
|
||||||
const String flutterRoot = '/flutter';
|
const String flutterRoot = '/flutter';
|
||||||
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
const String checkoutsParentDirectory = '$flutterRoot/dev/conductor';
|
||||||
const String candidateBranch = 'flutter-1.2-candidate.3';
|
const String candidateBranch = 'flutter-1.2-candidate.3';
|
||||||
|
const String workingBranch = 'cherrypicks-$candidateBranch';
|
||||||
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
||||||
final String localOperatingSystem = const LocalPlatform().pathSeparator;
|
final String localOperatingSystem = const LocalPlatform().pathSeparator;
|
||||||
const String revision1 = 'abc123';
|
const String revision1 = 'abc123';
|
||||||
|
const String revision2 = 'def456';
|
||||||
|
const String revision3 = '789aaa';
|
||||||
|
const String releaseVersion = '1.2.0-3.0.pre';
|
||||||
|
const String releaseChannel = 'beta';
|
||||||
MemoryFileSystem fileSystem;
|
MemoryFileSystem fileSystem;
|
||||||
TestStdio stdio;
|
TestStdio stdio;
|
||||||
const String stateFile = '/state-file.json';
|
const String stateFile = '/state-file.json';
|
||||||
@ -72,7 +78,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not prompt user and updates state.currentPhase from APPLY_ENGINE_CHERRYPICKS to CODESIGN_ENGINE_BINARIES if there are no engine cherrypicks', () async {
|
group('APPLY_ENGINE_CHERRYPICKS to CODESIGN_ENGINE_BINARIES', () {
|
||||||
|
test('does not prompt user and updates currentPhase if there are no engine cherrypicks', () async {
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
<FakeCommand>[],
|
<FakeCommand>[],
|
||||||
);
|
);
|
||||||
@ -109,17 +116,76 @@ void main() {
|
|||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
||||||
expect(stdio.error, isEmpty);
|
expect(stdio.error, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('confirms to stdout when all engine cherrypicks were auto-applied', () async {
|
||||||
|
const String remoteUrl = 'https://githost.com/org/repo.git';
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.empty();
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
engine: pb.Repository(
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(
|
||||||
|
trunkRevision: 'abc123',
|
||||||
|
state: pb.CherrypickState.COMPLETED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: remoteUrl),
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: remoteUrl),
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
writeStateToFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
state,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||||
|
await runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]);
|
||||||
|
|
||||||
test('updates state.lastPhase from APPLY_ENGINE_CHERRYPICKS to CODESIGN_ENGINE_BINARIES if user responds yes', () async {
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('All engine cherrypicks have been auto-applied by the conductor'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates lastPhase if user responds yes', () async {
|
||||||
const String remoteUrl = 'https://githost.com/org/repo.git';
|
const String remoteUrl = 'https://githost.com/org/repo.git';
|
||||||
stdio.stdin.add('y');
|
stdio.stdin.add('y');
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
<FakeCommand>[],
|
const FakeCommand(
|
||||||
);
|
command: <String>['git', 'fetch', 'upstream'],
|
||||||
|
),
|
||||||
|
const FakeCommand(command: <String>['git', 'checkout', 'upstream/$workingBranch']),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
const FakeCommand(command: <String>['git', 'push', 'mirror', '$revision1:$workingBranch']),
|
||||||
|
]);
|
||||||
final FakePlatform platform = FakePlatform(
|
final FakePlatform platform = FakePlatform(
|
||||||
environment: <String, String>{
|
environment: <String, String>{
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
@ -135,7 +201,9 @@ void main() {
|
|||||||
state: pb.CherrypickState.PENDING,
|
state: pb.CherrypickState.PENDING,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
mirror: pb.Remote(url: remoteUrl),
|
workingBranch: workingBranch,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: remoteUrl),
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: remoteUrl),
|
||||||
),
|
),
|
||||||
currentPhase: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
currentPhase: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
||||||
);
|
);
|
||||||
@ -162,25 +230,21 @@ void main() {
|
|||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
expect(stdio.stdout, contains(
|
expect(stdio.stdout, contains(
|
||||||
'Are you ready to push your engine branch to the repository $remoteUrl? (y/n) '));
|
'Are you ready to push your engine branch to the repository $remoteUrl? (y/n) '));
|
||||||
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
||||||
expect(stdio.error, isEmpty);
|
expect(stdio.error, isEmpty);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('does not update state.currentPhase from CODESIGN_ENGINE_BINARIES if user responds no', () async {
|
group('CODESIGN_ENGINE_BINARIES to APPLY_FRAMEWORK_CHERRYPICKS', () {
|
||||||
stdio.stdin.add('n');
|
pb.ConductorState state;
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
FakeProcessManager processManager;
|
||||||
<FakeCommand>[],
|
FakePlatform platform;
|
||||||
);
|
|
||||||
final FakePlatform platform = FakePlatform(
|
setUp(() {
|
||||||
environment: <String, String>{
|
state = pb.ConductorState(
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
|
||||||
},
|
|
||||||
operatingSystem: localOperatingSystem,
|
|
||||||
pathSeparator: localPathSeparator,
|
|
||||||
);
|
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
engine: pb.Repository(
|
engine: pb.Repository(
|
||||||
cherrypicks: <pb.Cherrypick>[
|
cherrypicks: <pb.Cherrypick>[
|
||||||
pb.Cherrypick(
|
pb.Cherrypick(
|
||||||
@ -191,6 +255,20 @@ void main() {
|
|||||||
),
|
),
|
||||||
currentPhase: ReleasePhase.CODESIGN_ENGINE_BINARIES,
|
currentPhase: ReleasePhase.CODESIGN_ENGINE_BINARIES,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
processManager = FakeProcessManager.empty();
|
||||||
|
|
||||||
|
platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update currentPhase if user responds no', () async {
|
||||||
|
stdio.stdin.add('n');
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -219,11 +297,8 @@ void main() {
|
|||||||
expect(stdio.error.contains('Aborting command.'), true);
|
expect(stdio.error.contains('Aborting command.'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates state.currentPhase from CODESIGN_ENGINE_BINARIES to APPLY_FRAMEWORK_CHERRYPICKS if user responds yes', () async {
|
test('updates currentPhase if user responds yes', () async {
|
||||||
stdio.stdin.add('y');
|
stdio.stdin.add('y');
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
|
||||||
<FakeCommand>[],
|
|
||||||
);
|
|
||||||
final FakePlatform platform = FakePlatform(
|
final FakePlatform platform = FakePlatform(
|
||||||
environment: <String, String>{
|
environment: <String, String>{
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
@ -231,9 +306,6 @@ void main() {
|
|||||||
operatingSystem: localOperatingSystem,
|
operatingSystem: localOperatingSystem,
|
||||||
pathSeparator: localPathSeparator,
|
pathSeparator: localPathSeparator,
|
||||||
);
|
);
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
currentPhase: ReleasePhase.CODESIGN_ENGINE_BINARIES,
|
|
||||||
);
|
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -260,74 +332,208 @@ void main() {
|
|||||||
expect(stdio.stdout, contains('Has CI passed for the engine PR and binaries been codesigned? (y/n) '));
|
expect(stdio.stdout, contains('Has CI passed for the engine PR and binaries been codesigned? (y/n) '));
|
||||||
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not prompt user and updates state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION if there are no framework cherrypicks', () async {
|
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
|
||||||
<FakeCommand>[],
|
|
||||||
);
|
|
||||||
final FakePlatform platform = FakePlatform(
|
|
||||||
environment: <String, String>{
|
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
|
||||||
},
|
|
||||||
operatingSystem: localOperatingSystem,
|
|
||||||
pathSeparator: localPathSeparator,
|
|
||||||
);
|
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
|
||||||
);
|
|
||||||
writeStateToFile(
|
|
||||||
fileSystem.file(stateFile),
|
|
||||||
state,
|
|
||||||
<String>[],
|
|
||||||
);
|
|
||||||
final Checkouts checkouts = Checkouts(
|
|
||||||
fileSystem: fileSystem,
|
|
||||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
stdio: stdio,
|
|
||||||
);
|
|
||||||
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
|
||||||
await runner.run(<String>[
|
|
||||||
'next',
|
|
||||||
'--$kStateOption',
|
|
||||||
stateFile,
|
|
||||||
]);
|
|
||||||
|
|
||||||
final pb.ConductorState finalState = readStateFromFile(
|
|
||||||
fileSystem.file(stateFile),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(stdio.stdout, isNot(contains('Did you apply all framework cherrypicks? (y/n) ')));
|
|
||||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
|
||||||
expect(stdio.error, isEmpty);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not update state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS if user responds no', () async {
|
group('APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION', () {
|
||||||
const String remoteUrl = 'https://githost.com/org/repo.git';
|
const String mirrorRemoteUrl = 'https://githost.com/org/repo.git';
|
||||||
stdio.stdin.add('n');
|
const String upstreamRemoteUrl = 'https://githost.com/mirror/repo.git';
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
const String engineUpstreamRemoteUrl = 'https://githost.com/mirror/engine.git';
|
||||||
<FakeCommand>[],
|
const String frameworkCheckoutPath = '$checkoutsParentDirectory/framework';
|
||||||
);
|
const String engineCheckoutPath = '$checkoutsParentDirectory/engine';
|
||||||
final FakePlatform platform = FakePlatform(
|
const String oldEngineVersion = '000000001';
|
||||||
|
FakeProcessManager processManager;
|
||||||
|
FakePlatform platform;
|
||||||
|
pb.ConductorState state;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
processManager = FakeProcessManager.empty();
|
||||||
|
platform = FakePlatform(
|
||||||
environment: <String, String>{
|
environment: <String, String>{
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
},
|
},
|
||||||
operatingSystem: localOperatingSystem,
|
operatingSystem: localOperatingSystem,
|
||||||
pathSeparator: localPathSeparator,
|
pathSeparator: localPathSeparator,
|
||||||
);
|
);
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
state = pb.ConductorState(
|
||||||
|
releaseChannel: releaseChannel,
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
framework: pb.Repository(
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: frameworkCheckoutPath,
|
||||||
cherrypicks: <pb.Cherrypick>[
|
cherrypicks: <pb.Cherrypick>[
|
||||||
pb.Cherrypick(
|
pb.Cherrypick(
|
||||||
trunkRevision: 'abc123',
|
trunkRevision: 'abc123',
|
||||||
state: pb.CherrypickState.PENDING,
|
state: pb.CherrypickState.PENDING,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
mirror: pb.Remote(url: remoteUrl),
|
mirror: pb.Remote(name: 'mirror', url: mirrorRemoteUrl),
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: upstreamRemoteUrl),
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
),
|
||||||
|
engine: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: engineCheckoutPath,
|
||||||
|
dartRevision: 'cdef0123',
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: engineUpstreamRemoteUrl),
|
||||||
),
|
),
|
||||||
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
);
|
);
|
||||||
|
// create engine repo
|
||||||
|
fileSystem.directory(engineCheckoutPath).createSync(recursive: true);
|
||||||
|
// create framework repo
|
||||||
|
final Directory frameworkDir = fileSystem.directory(frameworkCheckoutPath);
|
||||||
|
final File engineRevisionFile = frameworkDir
|
||||||
|
.childDirectory('bin')
|
||||||
|
.childDirectory('internal')
|
||||||
|
.childFile('engine.version');
|
||||||
|
engineRevisionFile.createSync(recursive: true);
|
||||||
|
engineRevisionFile.writeAsStringSync(oldEngineVersion, flush: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with no dart, engine or framework cherrypicks, no user input, no PR needed', () async {
|
||||||
|
state = pb.ConductorState(
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: frameworkCheckoutPath,
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: mirrorRemoteUrl),
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: upstreamRemoteUrl),
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
),
|
||||||
|
engine: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: engineCheckoutPath,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: engineUpstreamRemoteUrl),
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
|
||||||
|
writeStateToFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
state,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||||
|
|
||||||
|
await runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
final pb.ConductorState finalState = readStateFromFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('pull request is not required'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with no engine cherrypicks but a dart revision update, updates engine revision', () async {
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
processManager.addCommands(const <FakeCommand>[
|
||||||
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$candidateBranch']),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$workingBranch']),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision2,
|
||||||
|
),
|
||||||
|
FakeCommand(command: <String>['git', 'add', '--all']),
|
||||||
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'commit',
|
||||||
|
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision3,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
releaseChannel: releaseChannel,
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: frameworkCheckoutPath,
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: mirrorRemoteUrl),
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: upstreamRemoteUrl),
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
),
|
||||||
|
engine: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
checkoutPath: engineCheckoutPath,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: engineUpstreamRemoteUrl),
|
||||||
|
dartRevision: 'abc123',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
writeStateToFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
state,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||||
|
await runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(stdio.stdout, contains('Updating engine revision from $oldEngineVersion to $revision1'));
|
||||||
|
expect(stdio.stdout, contains('a framework PR is still\nrequired to roll engine cherrypicks.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update state.currentPhase if user responds no', () async {
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
processManager.addCommands(const <FakeCommand>[
|
||||||
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$candidateBranch']),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$workingBranch']),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision2,
|
||||||
|
),
|
||||||
|
FakeCommand(command: <String>['git', 'add', '--all']),
|
||||||
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'commit',
|
||||||
|
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision3,
|
||||||
|
),
|
||||||
|
]);
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -351,36 +557,42 @@ void main() {
|
|||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(stdio.stdout, contains('Are you ready to push your framework branch to the repository $remoteUrl? (y/n) '));
|
expect(stdio.stdout, contains('Are you ready to push your framework branch to the repository $mirrorRemoteUrl? (y/n) '));
|
||||||
expect(stdio.error, contains('Aborting command.'));
|
expect(stdio.error, contains('Aborting command.'));
|
||||||
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION if user responds yes', () async {
|
test('updates state.currentPhase if user responds yes', () async {
|
||||||
const String remoteUrl = 'https://githost.com/org/repo.git';
|
|
||||||
stdio.stdin.add('y');
|
stdio.stdin.add('y');
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
processManager.addCommands(const <FakeCommand>[
|
||||||
<FakeCommand>[],
|
// Engine repo
|
||||||
);
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
final FakePlatform platform = FakePlatform(
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$candidateBranch']),
|
||||||
environment: <String, String>{
|
FakeCommand(
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
},
|
stdout: revision1,
|
||||||
operatingSystem: localOperatingSystem,
|
|
||||||
pathSeparator: localPathSeparator,
|
|
||||||
);
|
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
framework: pb.Repository(
|
|
||||||
cherrypicks: <pb.Cherrypick>[
|
|
||||||
pb.Cherrypick(
|
|
||||||
trunkRevision: 'abc123',
|
|
||||||
state: pb.CherrypickState.PENDING,
|
|
||||||
),
|
),
|
||||||
],
|
// Framework repo
|
||||||
mirror: pb.Remote(url: remoteUrl),
|
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||||
|
FakeCommand(command: <String>['git', 'checkout', 'upstream/$workingBranch']),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision2,
|
||||||
),
|
),
|
||||||
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
FakeCommand(command: <String>['git', 'add', '--all']),
|
||||||
);
|
FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'commit',
|
||||||
|
"--message='Update Engine revision to $revision1 for $releaseChannel release $releaseVersion'",
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision3,
|
||||||
|
),
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['git', 'push', 'mirror', '$revision2:$workingBranch'],
|
||||||
|
),
|
||||||
|
]);
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -405,15 +617,57 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||||
expect(stdio.stdout, contains('Are you ready to push your framework branch to the repository $remoteUrl? (y/n)'));
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('Rolling new engine hash $revision1 to framework checkout...'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('There were 1 cherrypicks that were not auto-applied'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('Are you ready to push your framework branch to the repository $mirrorRemoteUrl? (y/n)'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('Executed command: `git push mirror $revision2:$workingBranch`'),
|
||||||
|
);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('PUBLISH_VERSION to PUBLISH_CHANNEL', () {
|
||||||
test('does not update state.currentPhase from PUBLISH_VERSION if user responds no', () async {
|
|
||||||
const String remoteName = 'upstream';
|
const String remoteName = 'upstream';
|
||||||
|
const String releaseVersion = '1.2.0-3.0.pre';
|
||||||
|
pb.ConductorState state;
|
||||||
|
FakePlatform platform;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.PUBLISH_VERSION,
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
||||||
|
),
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
|
);
|
||||||
|
platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update state.currentPhase if user responds no', () async {
|
||||||
stdio.stdin.add('n');
|
stdio.stdin.add('n');
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
<FakeCommand>[
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'fetch', 'upstream'],
|
||||||
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
),
|
),
|
||||||
@ -423,20 +677,6 @@ void main() {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
final FakePlatform platform = FakePlatform(
|
|
||||||
environment: <String, String>{
|
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
|
||||||
},
|
|
||||||
operatingSystem: localOperatingSystem,
|
|
||||||
pathSeparator: localPathSeparator,
|
|
||||||
);
|
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
currentPhase: ReleasePhase.PUBLISH_VERSION,
|
|
||||||
framework: pb.Repository(
|
|
||||||
candidateBranch: candidateBranch,
|
|
||||||
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -460,18 +700,19 @@ void main() {
|
|||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(stdio.stdout, contains('Has CI passed for the framework PR?'));
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(stdio.stdout, contains('Are you ready to tag commit $revision1 as $releaseVersion'));
|
||||||
expect(stdio.error, contains('Aborting command.'));
|
expect(stdio.error, contains('Aborting command.'));
|
||||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||||
expect(finalState.logs, stdio.logs);
|
expect(finalState.logs, stdio.logs);
|
||||||
expect(processManager.hasRemainingExpectations, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates state.currentPhase from PUBLISH_VERSION to PUBLISH_CHANNEL if user responds yes', () async {
|
test('updates state.currentPhase if user responds yes', () async {
|
||||||
const String remoteName = 'upstream';
|
|
||||||
const String releaseVersion = '1.2.0-3.0.pre';
|
|
||||||
stdio.stdin.add('y');
|
stdio.stdin.add('y');
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'fetch', 'upstream'],
|
||||||
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
),
|
),
|
||||||
@ -493,14 +734,6 @@ void main() {
|
|||||||
operatingSystem: localOperatingSystem,
|
operatingSystem: localOperatingSystem,
|
||||||
pathSeparator: localPathSeparator,
|
pathSeparator: localPathSeparator,
|
||||||
);
|
);
|
||||||
final pb.ConductorState state = pb.ConductorState(
|
|
||||||
currentPhase: ReleasePhase.PUBLISH_VERSION,
|
|
||||||
framework: pb.Repository(
|
|
||||||
candidateBranch: candidateBranch,
|
|
||||||
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
|
||||||
),
|
|
||||||
releaseVersion: releaseVersion,
|
|
||||||
);
|
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
state,
|
state,
|
||||||
@ -524,16 +757,139 @@ void main() {
|
|||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_CHANNEL);
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_CHANNEL);
|
||||||
expect(stdio.stdout, contains('Has CI passed for the framework PR?'));
|
expect(stdio.stdout, contains('Are you ready to tag commit $revision1 as $releaseVersion'));
|
||||||
expect(finalState.logs, stdio.logs);
|
expect(finalState.logs, stdio.logs);
|
||||||
expect(processManager.hasRemainingExpectations, false);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('PUBLISH_CHANNEL to VERIFY_RELEASE', () {
|
||||||
|
const String remoteName = 'upstream';
|
||||||
|
pb.ConductorState state;
|
||||||
|
FakePlatform platform;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.PUBLISH_CHANNEL,
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
||||||
|
),
|
||||||
|
releaseChannel: releaseChannel,
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
|
);
|
||||||
|
platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update currentPhase if user responds no', () async {
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'fetch', 'upstream'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
writeStateToFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
state,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||||
|
await runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
final pb.ConductorState finalState = readStateFromFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(stdio.error, contains('Aborting command.'));
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('About to execute command: `git push ${FrameworkRepository.defaultUpstream} $revision1:$releaseChannel`'),
|
||||||
|
);
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_CHANNEL);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates currentPhase if user responds yes', () async {
|
||||||
|
stdio.stdin.add('y');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'fetch', 'upstream'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'push', FrameworkRepository.defaultUpstream, '$revision1:$releaseChannel'],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
writeStateToFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
state,
|
||||||
|
<String>[],
|
||||||
|
);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory)..createSync(recursive: true),
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||||
|
await runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
final pb.ConductorState finalState = readStateFromFile(
|
||||||
|
fileSystem.file(stateFile),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager, hasNoRemainingExpectations);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('About to execute command: `git push ${FrameworkRepository.defaultUpstream} $revision1:$releaseChannel`'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
stdio.stdout,
|
||||||
|
contains('Release archive packages must be verified on cloud storage: https://ci.chromium.org/p/flutter/g/beta_packaging/console'),
|
||||||
|
);
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.VERIFY_RELEASE);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws exception if state.currentPhase is RELEASE_COMPLETED', () async {
|
test('throws exception if state.currentPhase is RELEASE_COMPLETED', () async {
|
||||||
final FakeProcessManager processManager = FakeProcessManager.list(
|
final FakeProcessManager processManager = FakeProcessManager.empty();
|
||||||
<FakeCommand>[],
|
|
||||||
);
|
|
||||||
final FakePlatform platform = FakePlatform(
|
final FakePlatform platform = FakePlatform(
|
||||||
environment: <String, String>{
|
environment: <String, String>{
|
||||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
Loading…
Reference in New Issue
Block a user