[flutter_conductor] auto-generate PR title and description via query params (#87313)
This commit is contained in:
parent
67cee63087
commit
da93ab36ed
@ -7,6 +7,8 @@ import 'package:file/file.dart';
|
|||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import 'proto/conductor_state.pb.dart' as pb;
|
||||||
|
|
||||||
const String kUpstreamRemote = 'https://github.com/flutter/flutter.git';
|
const String kUpstreamRemote = 'https://github.com/flutter/flutter.git';
|
||||||
|
|
||||||
const String gsutilBinary = 'gsutil.py';
|
const String gsutilBinary = 'gsutil.py';
|
||||||
@ -140,11 +142,60 @@ String fromArgToEnvName(String argName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a web link for the user to open a new PR.
|
/// Return a web link for the user to open a new PR.
|
||||||
|
///
|
||||||
|
/// Includes PR title and body via query params.
|
||||||
String getNewPrLink({
|
String getNewPrLink({
|
||||||
required String userName,
|
required String userName,
|
||||||
required String repoName,
|
required String repoName,
|
||||||
required String candidateBranch,
|
required pb.ConductorState state,
|
||||||
required String workingBranch,
|
|
||||||
}) {
|
}) {
|
||||||
return 'https://github.com/flutter/$repoName/compare/$candidateBranch...$userName:$workingBranch?expand=1';
|
assert(state.releaseChannel.isNotEmpty);
|
||||||
|
assert(state.releaseVersion.isNotEmpty);
|
||||||
|
late final String candidateBranch;
|
||||||
|
late final String workingBranch;
|
||||||
|
late final String repoLabel;
|
||||||
|
switch (repoName) {
|
||||||
|
case 'flutter':
|
||||||
|
candidateBranch = state.framework.candidateBranch;
|
||||||
|
workingBranch = state.framework.workingBranch;
|
||||||
|
repoLabel = 'Framework';
|
||||||
|
break;
|
||||||
|
case 'engine':
|
||||||
|
candidateBranch = state.engine.candidateBranch;
|
||||||
|
workingBranch = state.engine.workingBranch;
|
||||||
|
repoLabel = 'Engine';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ConductorException('Expected repoName to be one of flutter or engine but got $repoName.');
|
||||||
|
}
|
||||||
|
assert(candidateBranch.isNotEmpty);
|
||||||
|
assert(workingBranch.isNotEmpty);
|
||||||
|
final String title = '[flutter_releases] Flutter ${state.releaseChannel} '
|
||||||
|
'${state.releaseVersion} $repoLabel Cherrypicks';
|
||||||
|
final StringBuffer body = StringBuffer();
|
||||||
|
body.write('''
|
||||||
|
# Flutter ${state.releaseChannel} ${state.releaseVersion} $repoLabel
|
||||||
|
|
||||||
|
## Scheduled Cherrypicks
|
||||||
|
|
||||||
|
''');
|
||||||
|
if (repoName == 'engine') {
|
||||||
|
if (state.engine.dartRevision.isNotEmpty) {
|
||||||
|
// shorten hashes to make final link manageable
|
||||||
|
body.writeln('- Roll dart revision: dart-lang/sdk@${state.engine.dartRevision.substring(0, 9)}');
|
||||||
|
}
|
||||||
|
body.writeAll(
|
||||||
|
state.engine.cherrypicks.map<String>((pb.Cherrypick cp) => '- commit: ${cp.trunkRevision.substring(0, 9)}'),
|
||||||
|
'\n',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
body.writeAll(
|
||||||
|
state.framework.cherrypicks.map<String>((pb.Cherrypick cp) => '- commit: ${cp.trunkRevision.substring(0, 9)}'),
|
||||||
|
'\n',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 'https://github.com/flutter/$repoName/compare/$candidateBranch...$userName:$workingBranch?'
|
||||||
|
'expand=1'
|
||||||
|
'&title=${Uri.encodeQueryComponent(title)}'
|
||||||
|
'&body=${Uri.encodeQueryComponent(body.toString())}';
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,7 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
final String newPrLink = getNewPrLink(
|
final String newPrLink = getNewPrLink(
|
||||||
userName: githubAccount(state.engine.mirror.url),
|
userName: githubAccount(state.engine.mirror.url),
|
||||||
repoName: 'engine',
|
repoName: 'engine',
|
||||||
candidateBranch: state.engine.candidateBranch,
|
state: state,
|
||||||
workingBranch: state.engine.workingBranch,
|
|
||||||
);
|
);
|
||||||
return <String>[
|
return <String>[
|
||||||
'Your working branch ${state.engine.workingBranch} was pushed to your mirror.',
|
'Your working branch ${state.engine.workingBranch} was pushed to your mirror.',
|
||||||
@ -170,9 +169,8 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
|
|
||||||
final String newPrLink = getNewPrLink(
|
final String newPrLink = getNewPrLink(
|
||||||
userName: githubAccount(state.framework.mirror.url),
|
userName: githubAccount(state.framework.mirror.url),
|
||||||
repoName: 'framework',
|
repoName: 'flutter',
|
||||||
candidateBranch: state.framework.candidateBranch,
|
state: state,
|
||||||
workingBranch: state.framework.workingBranch,
|
|
||||||
);
|
);
|
||||||
return <String>[
|
return <String>[
|
||||||
'Your working branch ${state.framework.workingBranch} was pushed to your mirror.',
|
'Your working branch ${state.framework.workingBranch} was pushed to your mirror.',
|
||||||
|
130
dev/conductor/test/globals_test.dart
Normal file
130
dev/conductor/test/globals_test.dart
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// 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 'package:conductor/globals.dart';
|
||||||
|
import 'package:conductor/proto/conductor_state.pb.dart' as pb;
|
||||||
|
|
||||||
|
import './common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('assertsEnabled returns true in test suite', () {
|
||||||
|
expect(assertsEnabled(), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getNewPrLink', () {
|
||||||
|
const String userName = 'flutterer';
|
||||||
|
const String releaseChannel = 'beta';
|
||||||
|
const String releaseVersion = '1.2.0-3.4.pre';
|
||||||
|
const String candidateBranch = 'flutter-1.2-candidate.3';
|
||||||
|
const String workingBranch = 'cherrypicks-$candidateBranch';
|
||||||
|
const String dartRevision = 'fe9708ab688dcda9923f584ba370a66fcbc3811f';
|
||||||
|
const String engineCherrypick1 = 'a5a25cd702b062c24b2c67b8d30b5cb33e0ef6f0';
|
||||||
|
const String engineCherrypick2 = '94d06a2e1d01a3b0c693b94d70c5e1df9d78d249';
|
||||||
|
const String frameworkCherrypick =
|
||||||
|
'a5a25cd702b062c24b2c67b8d30b5cb33e0ef6f0';
|
||||||
|
|
||||||
|
final RegExp titlePattern = RegExp(r'&title=(.*)&');
|
||||||
|
final RegExp bodyPattern = RegExp(r'&body=(.*)$');
|
||||||
|
|
||||||
|
late pb.ConductorState state;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
state = pb.ConductorState(
|
||||||
|
engine: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(trunkRevision: engineCherrypick1),
|
||||||
|
pb.Cherrypick(trunkRevision: engineCherrypick2),
|
||||||
|
],
|
||||||
|
dartRevision: dartRevision,
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
),
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(trunkRevision: frameworkCherrypick),
|
||||||
|
],
|
||||||
|
workingBranch: workingBranch,
|
||||||
|
),
|
||||||
|
releaseChannel: releaseChannel,
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws on an invalid repoName', () {
|
||||||
|
expect(
|
||||||
|
() => getNewPrLink(
|
||||||
|
repoName: 'flooter',
|
||||||
|
userName: userName,
|
||||||
|
state: state,
|
||||||
|
),
|
||||||
|
throwsExceptionWith(
|
||||||
|
'Expected repoName to be one of flutter or engine but got flooter.',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns a valid URL for engine', () {
|
||||||
|
final String link = getNewPrLink(
|
||||||
|
repoName: 'engine',
|
||||||
|
userName: userName,
|
||||||
|
state: state,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
link,
|
||||||
|
contains('https://github.com/flutter/engine/compare/'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
link,
|
||||||
|
contains('$candidateBranch...$userName:$workingBranch?expand=1'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
Uri.decodeQueryComponent(
|
||||||
|
titlePattern.firstMatch(link)?.group(1) ?? ''),
|
||||||
|
'[flutter_releases] Flutter $releaseChannel $releaseVersion Engine Cherrypicks');
|
||||||
|
final String expectedBody = '''
|
||||||
|
# Flutter $releaseChannel $releaseVersion Engine
|
||||||
|
|
||||||
|
## Scheduled Cherrypicks
|
||||||
|
|
||||||
|
- Roll dart revision: dart-lang/sdk@${dartRevision.substring(0, 9)}
|
||||||
|
- commit: ${engineCherrypick1.substring(0, 9)}
|
||||||
|
- commit: ${engineCherrypick2.substring(0, 9)}''';
|
||||||
|
expect(
|
||||||
|
Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''),
|
||||||
|
expectedBody,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns a valid URL for framework', () {
|
||||||
|
final String link = getNewPrLink(
|
||||||
|
repoName: 'flutter',
|
||||||
|
userName: userName,
|
||||||
|
state: state,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
link,
|
||||||
|
contains('https://github.com/flutter/flutter/compare/'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
link,
|
||||||
|
contains('$candidateBranch...$userName:$workingBranch?expand=1'),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
Uri.decodeQueryComponent(
|
||||||
|
titlePattern.firstMatch(link)?.group(1) ?? ''),
|
||||||
|
'[flutter_releases] Flutter $releaseChannel $releaseVersion Framework Cherrypicks');
|
||||||
|
final String expectedBody = '''
|
||||||
|
# Flutter $releaseChannel $releaseVersion Framework
|
||||||
|
|
||||||
|
## Scheduled Cherrypicks
|
||||||
|
|
||||||
|
- commit: ${frameworkCherrypick.substring(0, 9)}''';
|
||||||
|
expect(
|
||||||
|
Uri.decodeQueryComponent(bodyPattern.firstMatch(link)?.group(1) ?? ''),
|
||||||
|
expectedBody,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -23,9 +23,9 @@ void main() {
|
|||||||
const String workingBranch = 'cherrypicks-$candidateBranch';
|
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 = 'd3af60d18e01fcb36e0c0fa06c8502e4935ed095';
|
||||||
const String revision2 = 'def456';
|
const String revision2 = 'f99555c1e1392bf2a8135056b9446680c2af4ddf';
|
||||||
const String revision3 = '789aaa';
|
const String revision3 = '98a5ca242b9d270ce000b26309b8a3cdc9c89df5';
|
||||||
const String releaseVersion = '1.2.0-3.0.pre';
|
const String releaseVersion = '1.2.0-3.0.pre';
|
||||||
const String releaseChannel = 'beta';
|
const String releaseChannel = 'beta';
|
||||||
late MemoryFileSystem fileSystem;
|
late MemoryFileSystem fileSystem;
|
||||||
@ -203,7 +203,7 @@ void main() {
|
|||||||
candidateBranch: candidateBranch,
|
candidateBranch: candidateBranch,
|
||||||
cherrypicks: <pb.Cherrypick>[
|
cherrypicks: <pb.Cherrypick>[
|
||||||
pb.Cherrypick(
|
pb.Cherrypick(
|
||||||
trunkRevision: 'abc123',
|
trunkRevision: revision2,
|
||||||
state: pb.CherrypickState.PENDING,
|
state: pb.CherrypickState.PENDING,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -212,6 +212,7 @@ void main() {
|
|||||||
mirror: pb.Remote(name: 'mirror', url: remoteUrl),
|
mirror: pb.Remote(name: 'mirror', url: remoteUrl),
|
||||||
),
|
),
|
||||||
releaseChannel: releaseChannel,
|
releaseChannel: releaseChannel,
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
);
|
);
|
||||||
writeStateToFile(
|
writeStateToFile(
|
||||||
fileSystem.file(stateFile),
|
fileSystem.file(stateFile),
|
||||||
@ -350,6 +351,7 @@ void main() {
|
|||||||
const String frameworkCheckoutPath = '$checkoutsParentDirectory/framework';
|
const String frameworkCheckoutPath = '$checkoutsParentDirectory/framework';
|
||||||
const String engineCheckoutPath = '$checkoutsParentDirectory/engine';
|
const String engineCheckoutPath = '$checkoutsParentDirectory/engine';
|
||||||
const String oldEngineVersion = '000000001';
|
const String oldEngineVersion = '000000001';
|
||||||
|
const String frameworkCherrypick = '431ae69b4dd2dd48f7ba0153671e0311014c958b';
|
||||||
late FakeProcessManager processManager;
|
late FakeProcessManager processManager;
|
||||||
late FakePlatform platform;
|
late FakePlatform platform;
|
||||||
late pb.ConductorState state;
|
late pb.ConductorState state;
|
||||||
@ -371,7 +373,7 @@ void main() {
|
|||||||
checkoutPath: frameworkCheckoutPath,
|
checkoutPath: frameworkCheckoutPath,
|
||||||
cherrypicks: <pb.Cherrypick>[
|
cherrypicks: <pb.Cherrypick>[
|
||||||
pb.Cherrypick(
|
pb.Cherrypick(
|
||||||
trunkRevision: 'abc123',
|
trunkRevision: frameworkCherrypick,
|
||||||
state: pb.CherrypickState.PENDING,
|
state: pb.CherrypickState.PENDING,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -383,6 +385,7 @@ void main() {
|
|||||||
candidateBranch: candidateBranch,
|
candidateBranch: candidateBranch,
|
||||||
checkoutPath: engineCheckoutPath,
|
checkoutPath: engineCheckoutPath,
|
||||||
dartRevision: 'cdef0123',
|
dartRevision: 'cdef0123',
|
||||||
|
workingBranch: workingBranch,
|
||||||
upstream: pb.Remote(name: 'upstream', url: engineUpstreamRemoteUrl),
|
upstream: pb.Remote(name: 'upstream', url: engineUpstreamRemoteUrl),
|
||||||
),
|
),
|
||||||
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user