From ce1acf5dc0dc0e0b8041b53554b28bdccb08c939 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 19 Nov 2021 13:03:08 -0800 Subject: [PATCH] [flutter_conductor] implement requiredLocalBranches (#93580) --- dev/conductor/core/bin/cli.dart | 6 - dev/conductor/core/lib/conductor_core.dart | 1 - dev/conductor/core/lib/src/git.dart | 7 +- dev/conductor/core/lib/src/globals.dart | 3 +- dev/conductor/core/lib/src/repository.dart | 32 +- dev/conductor/core/lib/src/roll_dev.dart | 204 ----- dev/conductor/core/lib/src/start.dart | 2 +- .../core/test/codesign_integration_test.dart | 23 +- dev/conductor/core/test/codesign_test.dart | 25 + dev/conductor/core/test/repository_test.dart | 200 +++-- dev/conductor/core/test/roll_dev_test.dart | 708 ------------------ dev/conductor/core/test/start_test.dart | 12 +- 12 files changed, 238 insertions(+), 985 deletions(-) delete mode 100644 dev/conductor/core/lib/src/roll_dev.dart delete mode 100644 dev/conductor/core/test/roll_dev_test.dart diff --git a/dev/conductor/core/bin/cli.dart b/dev/conductor/core/bin/cli.dart index f3770dbf05c..0e86f13c79b 100644 --- a/dev/conductor/core/bin/cli.dart +++ b/dev/conductor/core/bin/cli.dart @@ -46,12 +46,6 @@ Future main(List args) async { )).trim(); >[ - RollDevCommand( - checkouts: checkouts, - fileSystem: fileSystem, - platform: platform, - stdio: stdio, - ), CodesignCommand( checkouts: checkouts, flutterRoot: _localFlutterRoot, diff --git a/dev/conductor/core/lib/conductor_core.dart b/dev/conductor/core/lib/conductor_core.dart index 9bb127659af..f1203be73f0 100644 --- a/dev/conductor/core/lib/conductor_core.dart +++ b/dev/conductor/core/lib/conductor_core.dart @@ -11,7 +11,6 @@ export 'src/git.dart'; export 'src/globals.dart'; export 'src/next.dart' hide kStateOption, kYesFlag; export 'src/repository.dart'; -export 'src/roll_dev.dart'; export 'src/start.dart' hide kStateOption; export 'src/state.dart'; export 'src/status.dart' hide kStateOption; diff --git a/dev/conductor/core/lib/src/git.dart b/dev/conductor/core/lib/src/git.dart index 5d39e2781c2..4418aabcce0 100644 --- a/dev/conductor/core/lib/src/git.dart +++ b/dev/conductor/core/lib/src/git.dart @@ -33,7 +33,12 @@ class Git { bool allowNonZeroExitCode = false, required String workingDirectory, }) async { - final ProcessResult result = await _run(args, workingDirectory); + late final ProcessResult result; + try { + result = await _run(args, workingDirectory); + } on ProcessException { + _reportFailureAndExit(args, workingDirectory, result, explanation); + } if (result.exitCode != 0 && !allowNonZeroExitCode) { _reportFailureAndExit(args, workingDirectory, result, explanation); } diff --git a/dev/conductor/core/lib/src/globals.dart b/dev/conductor/core/lib/src/globals.dart index 87619d90991..a5dd9de7994 100644 --- a/dev/conductor/core/lib/src/globals.dart +++ b/dev/conductor/core/lib/src/globals.dart @@ -5,6 +5,7 @@ import 'package:args/args.dart'; import 'proto/conductor_state.pb.dart' as pb; +import 'repository.dart'; const String gsutilBinary = 'gsutil.py'; @@ -15,7 +16,7 @@ const List kReleaseChannels = [ 'stable', 'beta', 'dev', - 'master', + FrameworkRepository.defaultBranch, ]; const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process'; diff --git a/dev/conductor/core/lib/src/repository.dart b/dev/conductor/core/lib/src/repository.dart index e1f9e087234..dc4b703a6f3 100644 --- a/dev/conductor/core/lib/src/repository.dart +++ b/dev/conductor/core/lib/src/repository.dart @@ -56,17 +56,24 @@ abstract class Repository { required this.platform, required this.fileSystem, required this.parentDirectory, + required this.requiredLocalBranches, this.initialRef, this.localUpstream = false, this.previousCheckoutLocation, this.mirrorRemote, }) : git = Git(processManager), - assert(localUpstream != null), assert(upstreamRemote.url.isNotEmpty); final String name; final Remote upstreamRemote; + /// Branches that must exist locally in this [Repository]. + /// + /// If this [Repository] is used as a local upstream for another, the + /// downstream may try to fetch these branches, and git will fail if they do + /// not exist. + final List requiredLocalBranches; + /// Remote for user's mirror. /// /// This value can be null, in which case attempting to access it will lead to @@ -117,11 +124,13 @@ abstract class Repository { workingDirectory: _checkoutDirectory!.path, ); } + return _checkoutDirectory!; } _checkoutDirectory = parentDirectory.childDirectory(name); await lazilyInitialize(_checkoutDirectory!); + return _checkoutDirectory!; } @@ -162,7 +171,7 @@ abstract class Repository { if (localUpstream) { // These branches must exist locally for the repo that depends on it // to fetch and push to. - for (final String channel in kReleaseChannels) { + for (final String channel in requiredLocalBranches) { await git.run( ['checkout', channel, '--'], 'check out branch $channel locally', @@ -173,7 +182,7 @@ abstract class Repository { if (initialRef != null) { await git.run( - ['checkout', '${upstreamRemote.name}/$initialRef'], + ['checkout', initialRef!], 'Checking out initialRef $initialRef', workingDirectory: checkoutDirectory.path, ); @@ -463,8 +472,9 @@ class FrameworkRepository extends Repository { name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream), bool localUpstream = false, String? previousCheckoutLocation, - String? initialRef, + String initialRef = FrameworkRepository.defaultBranch, Remote? mirrorRemote, + List? additionalRequiredLocalBranches, }) : super( name: name, upstreamRemote: upstreamRemote, @@ -477,6 +487,10 @@ class FrameworkRepository extends Repository { processManager: checkouts.processManager, stdio: checkouts.stdio, previousCheckoutLocation: previousCheckoutLocation, + requiredLocalBranches: [ + ...?additionalRequiredLocalBranches, + ...kReleaseChannels, + ], ); /// A [FrameworkRepository] with the host conductor's repo set as upstream. @@ -487,6 +501,7 @@ class FrameworkRepository extends Repository { Checkouts checkouts, { String name = 'framework', String? previousCheckoutLocation, + String initialRef = FrameworkRepository.defaultBranch, required String upstreamPath, }) { return FrameworkRepository( @@ -497,13 +512,12 @@ class FrameworkRepository extends Repository { url: 'file://$upstreamPath/', ), previousCheckoutLocation: previousCheckoutLocation, + initialRef: initialRef, ); } final Checkouts checkouts; - static const String defaultUpstream = - 'git@github.com:flutter/flutter.git'; - + static const String defaultUpstream = 'git@github.com:flutter/flutter.git'; static const String defaultBranch = 'master'; Future get ciYaml async { @@ -717,6 +731,7 @@ class EngineRepository extends Repository { bool localUpstream = false, String? previousCheckoutLocation, Remote? mirrorRemote, + List? additionalRequiredLocalBranches, }) : super( name: name, upstreamRemote: upstreamRemote, @@ -729,6 +744,7 @@ class EngineRepository extends Repository { processManager: checkouts.processManager, stdio: checkouts.stdio, previousCheckoutLocation: previousCheckoutLocation, + requiredLocalBranches: additionalRequiredLocalBranches ?? const [], ); final Checkouts checkouts; @@ -739,7 +755,7 @@ class EngineRepository extends Repository { } static const String defaultUpstream = 'git@github.com:flutter/engine.git'; - static const String defaultBranch = 'master'; + static const String defaultBranch = 'main'; /// Update the `dart_revision` entry in the DEPS file. Future updateDartRevision( diff --git a/dev/conductor/core/lib/src/roll_dev.dart b/dev/conductor/core/lib/src/roll_dev.dart deleted file mode 100644 index 41ae61ac891..00000000000 --- a/dev/conductor/core/lib/src/roll_dev.dart +++ /dev/null @@ -1,204 +0,0 @@ -// 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:args/args.dart'; -import 'package:args/command_runner.dart'; -import 'package:file/file.dart'; -import 'package:meta/meta.dart'; -import 'package:platform/platform.dart'; - -import './repository.dart'; -import './stdio.dart'; -import './version.dart'; - -const String kIncrement = 'increment'; -const String kCandidateBranch = 'candidate-branch'; -const String kRemoteName = 'remote'; -const String kJustPrint = 'just-print'; -const String kYes = 'yes'; -const String kForce = 'force'; -const String kSkipTagging = 'skip-tagging'; - -/// Create a new dev release without cherry picks. -class RollDevCommand extends Command { - RollDevCommand({ - required this.checkouts, - required this.fileSystem, - required this.platform, - required this.stdio, - }) { - argParser.addOption( - kIncrement, - help: 'Specifies which part of the x.y.z version number to increment. Required.', - valueHelp: 'level', - allowed: ['y', 'z', 'm'], - allowedHelp: { - 'y': 'Indicates the first dev release after a beta release.', - 'z': 'Indicates a hotfix to a stable release.', - 'm': 'Indicates a standard dev release.', - }, - ); - argParser.addOption( - kCandidateBranch, - help: 'Specifies which git branch to roll to the dev branch. Required.', - valueHelp: 'branch', - ); - argParser.addFlag( - kForce, - abbr: 'f', - help: 'Force push. Necessary when the previous release had cherry-picks.', - negatable: false, - ); - argParser.addFlag( - kJustPrint, - negatable: false, - help: - "Don't actually roll the dev channel; " - 'just print the would-be version and quit.', - ); - argParser.addFlag( - kSkipTagging, - negatable: false, - help: 'Do not create tag and push to remote, only update release branch. ' - 'For recovering when the script fails trying to git push to the release branch.' - ); - argParser.addFlag( - kYes, - negatable: false, - abbr: 'y', - help: 'Skip the confirmation prompt.', - ); - argParser.addOption( - kRemoteName, - help: 'Specifies which git remote to fetch from.', - defaultsTo: 'upstream', - ); - } - - final Checkouts checkouts; - final FileSystem fileSystem; - final Platform platform; - final Stdio stdio; - - @override - String get name => 'roll-dev'; - - @override - String get description => - 'For publishing a dev release without cherry picks.'; - - @override - Future run() async { - await rollDev( - argResults: argResults!, - repository: FrameworkRepository(checkouts), - stdio: stdio, - usage: argParser.usage, - ); - } -} - -/// Main script execution. -/// -/// Returns true if publishing was successful, else false. -@visibleForTesting -Future rollDev({ - required String usage, - required ArgResults argResults, - required Stdio stdio, - required FrameworkRepository repository, -}) async { - final String remoteName = argResults[kRemoteName] as String; - final String? level = argResults[kIncrement] as String?; - final String candidateBranch = argResults[kCandidateBranch] as String; - final bool justPrint = argResults[kJustPrint] as bool; - final bool autoApprove = argResults[kYes] as bool; - final bool force = argResults[kForce] as bool; - final bool skipTagging = argResults[kSkipTagging] as bool; - - if (level == null || candidateBranch == null) { - throw Exception( - 'roll_dev.dart --$kIncrement=level --$kCandidateBranch=branch • update the version tags ' - 'and roll a new dev build.\n$usage'); - } - - final String remoteUrl = await repository.remoteUrl(remoteName); - - if (!(await repository.gitCheckoutClean())) { - throw Exception( - 'Your git repository is not clean. Try running "git clean -fd". Warning, ' - 'this will delete files! Run with -n to find out which ones.'); - } - - await repository.fetch(remoteName); - - // Verify [commit] is valid - final String commit = await repository.reverseParse(candidateBranch); - - stdio.printStatus('remoteName is $remoteName'); - // Get the name of the last dev release - final Version lastVersion = Version.fromString( - await repository.getFullTag(remoteName, 'dev'), - ); - - final Version version = - skipTagging ? lastVersion : Version.fromCandidateBranch(candidateBranch); - final String tagName = version.toString(); - - if ((await repository.reverseParse(lastVersion.toString())).contains(commit.trim())) { - throw Exception( - 'Commit $commit is already on the dev branch as $lastVersion.'); - } - - if (justPrint) { - stdio.printStatus(tagName); - return false; - } - - if (skipTagging && !(await repository.isCommitTagged(commit))) { - throw Exception( - 'The $kSkipTagging flag is only supported for tagged commits.'); - } - - if (!force && !(await repository.isAncestor(commit, lastVersion.toString()))) { - throw Exception( - 'The previous dev tag $lastVersion is not a direct ancestor of $commit.\n' - 'The flag "$kForce" is required to force push a new release past a cherry-pick.'); - } - - final String hash = await repository.reverseParse(commit); - - // [commit] can be a prefix for [hash]. - assert(hash.startsWith(commit)); - - // PROMPT - if (autoApprove) { - stdio.printStatus( - 'Publishing Flutter $version ($hash) to the "dev" channel.'); - } else { - stdio.printStatus('Your tree is ready to publish Flutter $version ' - '($hash) to the "dev" channel.'); - stdio.write('Are you? [yes/no] '); - if (stdio.readLineSync() != 'yes') { - stdio.printError('The dev roll has been aborted.'); - return false; - } - } - - if (!skipTagging) { - await repository.tag(commit, version.toString(), remoteName); - } - - await repository.pushRef( - fromRef: commit, - remote: remoteName, - toRef: 'dev', - force: force, - ); - - stdio.printStatus( - 'Flutter version $version has been rolled to the "dev" channel at $remoteUrl.', - ); - return true; -} diff --git a/dev/conductor/core/lib/src/start.dart b/dev/conductor/core/lib/src/start.dart index 47732aea000..7c57fd48f18 100644 --- a/dev/conductor/core/lib/src/start.dart +++ b/dev/conductor/core/lib/src/start.dart @@ -455,7 +455,7 @@ class StartContext { } final String branchPoint = await framework.branchPoint( candidateBranch, - kFrameworkDefaultBranch, + FrameworkRepository.defaultBranch, ); stdio.printStatus('Applying the tag $requestedVersion at the branch point $branchPoint'); await framework.tag( diff --git a/dev/conductor/core/test/codesign_integration_test.dart b/dev/conductor/core/test/codesign_integration_test.dart index 2e719ed36dd..15064c6b110 100644 --- a/dev/conductor/core/test/codesign_integration_test.dart +++ b/dev/conductor/core/test/codesign_integration_test.dart @@ -5,7 +5,7 @@ import 'package:args/command_runner.dart'; import 'package:conductor_core/src/codesign.dart' show CodesignCommand; import 'package:conductor_core/src/globals.dart'; -import 'package:conductor_core/src/repository.dart' show Checkouts; +import 'package:conductor_core/src/repository.dart' show Checkouts, FrameworkRepository; import 'package:file/file.dart'; import 'package:file/local.dart'; import 'package:platform/platform.dart'; @@ -35,9 +35,24 @@ void main() { fileSystem.file(platform.executable), ); - final CommandRunner runner = CommandRunner('codesign-test', '') - ..addCommand( - CodesignCommand(checkouts: checkouts, flutterRoot: flutterRoot)); + final String currentHead = (processManager.runSync( + ['git', 'rev-parse', 'HEAD'], + workingDirectory: flutterRoot.path, + ).stdout as String).trim(); + + final FrameworkRepository framework = FrameworkRepository.localRepoAsUpstream( + checkouts, + upstreamPath: flutterRoot.path, + initialRef: currentHead, + ); + final CommandRunner runner = CommandRunner('codesign-test', ''); + runner.addCommand( + CodesignCommand( + checkouts: checkouts, + framework: framework, + flutterRoot: flutterRoot, + ), + ); try { await runner.run([ diff --git a/dev/conductor/core/test/codesign_test.dart b/dev/conductor/core/test/codesign_test.dart index b85d94da722..a33cf7687fa 100644 --- a/dev/conductor/core/test/codesign_test.dart +++ b/dev/conductor/core/test/codesign_test.dart @@ -112,6 +112,11 @@ void main() { 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -196,6 +201,11 @@ void main() { 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -284,6 +294,11 @@ void main() { 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -371,6 +386,11 @@ void main() { 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -430,6 +450,11 @@ void main() { 'file://$flutterRoot/', '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', diff --git a/dev/conductor/core/test/repository_test.dart b/dev/conductor/core/test/repository_test.dart index 32a7429a67c..9c746180013 100644 --- a/dev/conductor/core/test/repository_test.dart +++ b/dev/conductor/core/test/repository_test.dart @@ -13,24 +13,27 @@ void main() { group('repository', () { late FakePlatform platform; const String rootDir = '/'; + late MemoryFileSystem fileSystem; + late FakeProcessManager processManager; + late TestStdio stdio; setUp(() { final String pathSeparator = const LocalPlatform().pathSeparator; + fileSystem = MemoryFileSystem.test(); platform = FakePlatform( environment: { 'HOME': ['path', 'to', 'home'].join(pathSeparator), }, pathSeparator: pathSeparator, ); + processManager = FakeProcessManager.empty(); + stdio = TestStdio(); }); test('canCherryPick returns true if git cherry-pick returns 0', () async { const String commit = 'abc123'; - final TestStdio stdio = TestStdio(); - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final FakeProcessManager processManager = - FakeProcessManager.list([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -41,6 +44,11 @@ void main() { fileSystem.path .join(rootDir, 'flutter_conductor_checkouts', 'framework'), ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -78,10 +86,7 @@ void main() { test('canCherryPick returns false if git cherry-pick returns non-zero', () async { const String commit = 'abc123'; - final TestStdio stdio = TestStdio(); - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final FakeProcessManager processManager = - FakeProcessManager.list([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -92,6 +97,11 @@ void main() { fileSystem.path .join(rootDir, 'flutter_conductor_checkouts', 'framework'), ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -133,10 +143,7 @@ void main() { test('cherryPick() applies the commit', () async { const String commit = 'abc123'; - final TestStdio stdio = TestStdio(); - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final FakeProcessManager processManager = - FakeProcessManager.list([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -147,6 +154,11 @@ void main() { fileSystem.path .join(rootDir, 'flutter_conductor_checkouts', 'framework'), ]), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -178,9 +190,6 @@ void main() { test('updateDartRevision() updates the DEPS file', () async { const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef'; const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e'; - final TestStdio stdio = TestStdio(); - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final FakeProcessManager processManager = FakeProcessManager.empty(); final Checkouts checkouts = Checkouts( fileSystem: fileSystem, @@ -200,9 +209,6 @@ void main() { test('updateDartRevision() throws exception on malformed DEPS file', () { const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e'; - final TestStdio stdio = TestStdio(); - final MemoryFileSystem fileSystem = MemoryFileSystem.test(); - final FakeProcessManager processManager = FakeProcessManager.empty(); final Checkouts checkouts = Checkouts( fileSystem: fileSystem, @@ -227,9 +233,7 @@ vars = { 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([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -243,7 +247,7 @@ vars = { const FakeCommand(command: [ 'git', 'checkout', - 'upstream/master', + EngineRepository.defaultBranch, ]), const FakeCommand(command: [ 'git', @@ -282,13 +286,11 @@ vars = { ); }); - test('commit() passes correct commit message', () { + test('commit() passes correct commit message', () async { 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([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -302,7 +304,7 @@ vars = { const FakeCommand(command: [ 'git', 'checkout', - 'upstream/master', + EngineRepository.defaultBranch, ]), const FakeCommand(command: [ 'git', @@ -334,16 +336,15 @@ vars = { ); final EngineRepository repo = EngineRepository(checkouts); - repo.commit(message); + await repo.commit(message); + expect(processManager.hasRemainingExpectations, false); }); test('updateEngineRevision() returns false if newCommit is the same as version file', () async { 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([ + processManager.addCommands([ FakeCommand(command: [ 'git', 'clone', @@ -375,7 +376,6 @@ vars = { }); 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( @@ -386,10 +386,8 @@ vars = { test('ciYaml.enableBranch() will prepend the given branch to the yaml list of enabled_branches', () async { 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([ + processManager.addCommands([ FakeCommand( command: [ 'git', @@ -407,12 +405,17 @@ vars = { # Friendly note enabled_branches: - - master + - ${FrameworkRepository.defaultBranch} - dev - beta - stable '''); }), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -430,13 +433,13 @@ enabled_branches: final FrameworkRepository framework = FrameworkRepository(checkouts); expect( (await framework.ciYaml).enabledBranches, - ['master', 'dev', 'beta', 'stable'], + [FrameworkRepository.defaultBranch, 'dev', 'beta', 'stable'], ); (await framework.ciYaml).enableBranch('foo'); expect( (await framework.ciYaml).enabledBranches, - ['foo', 'master', 'dev', 'beta', 'stable'], + ['foo', FrameworkRepository.defaultBranch, 'dev', 'beta', 'stable'], ); expect( @@ -446,7 +449,7 @@ enabled_branches: enabled_branches: - foo - - master + - ${FrameworkRepository.defaultBranch} - dev - beta - stable @@ -456,10 +459,8 @@ enabled_branches: 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([ + processManager.addCommands([ FakeCommand( command: [ 'git', @@ -475,12 +476,17 @@ enabled_branches: ciYaml.createSync(recursive: true); ciYaml.writeAsStringSync(''' enabled_branches: - - master + - ${FrameworkRepository.defaultBranch} - dev - beta - stable '''); }), + const FakeCommand(command: [ + 'git', + 'checkout', + FrameworkRepository.defaultBranch, + ]), const FakeCommand(command: [ 'git', 'rev-parse', @@ -497,10 +503,114 @@ enabled_branches: final FrameworkRepository framework = FrameworkRepository(checkouts); expect( - () async => (await framework.ciYaml).enableBranch('master'), - throwsExceptionWith('.ci.yaml already contains the branch master'), + () async => (await framework.ciYaml).enableBranch(FrameworkRepository.defaultBranch), + throwsExceptionWith('.ci.yaml already contains the branch ${FrameworkRepository.defaultBranch}'), ); }); + + test('framework repo set as localUpstream ensures requiredLocalBranches exist locally', () async { + const String commit = 'deadbeef'; + const String candidateBranch = 'flutter-1.2-candidate.3'; + bool createdCandidateBranch = false; + processManager.addCommands([ + FakeCommand(command: [ + 'git', + 'clone', + '--origin', + 'upstream', + '--', + FrameworkRepository.defaultUpstream, + fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'framework'), + ]), + FakeCommand( + command: const ['git', 'checkout', candidateBranch, '--'], + onRun: () => createdCandidateBranch = true, + ), + const FakeCommand( + command: ['git', 'checkout', 'stable', '--'], + ), + const FakeCommand( + command: ['git', 'checkout', 'beta', '--'], + ), + const FakeCommand( + command: ['git', 'checkout', 'dev', '--'], + ), + const FakeCommand( + command: ['git', 'checkout', FrameworkRepository.defaultBranch, '--'], + ), + const FakeCommand( + command: ['git', 'checkout', FrameworkRepository.defaultBranch], + ), + const FakeCommand( + command: ['git', 'rev-parse', 'HEAD'], + stdout: commit, + ), + ]); + final Checkouts checkouts = Checkouts( + fileSystem: fileSystem, + parentDirectory: fileSystem.directory(rootDir), + platform: platform, + processManager: processManager, + stdio: stdio, + ); + + final Repository repo = FrameworkRepository( + checkouts, + additionalRequiredLocalBranches: [candidateBranch], + localUpstream: true, + ); + // call this so that repo.lazilyInitialize() is called. + await repo.checkoutDirectory; + + expect(processManager.hasRemainingExpectations, false); + expect(createdCandidateBranch, true); + }); + + test('engine repo set as localUpstream ensures requiredLocalBranches exist locally', () async { + const String commit = 'deadbeef'; + const String candidateBranch = 'flutter-1.2-candidate.3'; + bool createdCandidateBranch = false; + processManager.addCommands([ + FakeCommand(command: [ + 'git', + 'clone', + '--origin', + 'upstream', + '--', + EngineRepository.defaultUpstream, + fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'engine'), + ]), + FakeCommand( + command: const ['git', 'checkout', candidateBranch, '--'], + onRun: () => createdCandidateBranch = true, + ), + const FakeCommand( + command: ['git', 'checkout', EngineRepository.defaultBranch], + ), + const FakeCommand( + command: ['git', 'rev-parse', 'HEAD'], + stdout: commit, + ), + ]); + final Checkouts checkouts = Checkouts( + fileSystem: fileSystem, + parentDirectory: fileSystem.directory(rootDir), + platform: platform, + processManager: processManager, + stdio: stdio, + ); + + final Repository repo = EngineRepository( + checkouts, + additionalRequiredLocalBranches: [candidateBranch], + localUpstream: true, + ); + // call this so that repo.lazilyInitialize() is called. + await repo.checkoutDirectory; + + expect(processManager.hasRemainingExpectations, false); + expect(createdCandidateBranch, true); + }); }); } diff --git a/dev/conductor/core/test/roll_dev_test.dart b/dev/conductor/core/test/roll_dev_test.dart deleted file mode 100644 index cc2b18640ee..00000000000 --- a/dev/conductor/core/test/roll_dev_test.dart +++ /dev/null @@ -1,708 +0,0 @@ -// 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_core/src/repository.dart'; -import 'package:conductor_core/src/roll_dev.dart'; -import 'package:file/memory.dart'; -import 'package:platform/platform.dart'; - -import './common.dart'; - -const String _kUpstreamRemote = 'git@github.com:flutter/flutter.git'; - -void main() { - group('rollDev()', () { - const String usage = 'usage info...'; - const String level = 'm'; - const String commit = 'abcde012345'; - const String remote = 'origin'; - const String lastVersion = '1.2.0-0.0.pre'; - const String nextVersion = '1.2.0-2.0.pre'; - const String candidateBranch = 'flutter-1.2-candidate.2'; - const String checkoutsParentDirectory = '/path/to/directory/'; - late FakeArgResults fakeArgResults; - late MemoryFileSystem fileSystem; - late TestStdio stdio; - late FrameworkRepository repo; - late Checkouts checkouts; - late FakePlatform platform; - late FakeProcessManager processManager; - - setUp(() { - stdio = TestStdio(); - fileSystem = MemoryFileSystem.test(); - platform = FakePlatform(); - processManager = FakeProcessManager.list([]); - checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - repo = FrameworkRepository(checkouts); - }); - - test('throws Exception if level not provided', () { - fakeArgResults = FakeArgResults( - level: null, - candidateBranch: candidateBranch, - remote: remote, - ); - expect( - () async => rollDev( - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - usage: usage, - ), - throwsExceptionWith(usage), - ); - }); - - test('throws exception if git checkout not clean', () { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ], stdout: ' M dev/conductor/bin/conductor.dart'), - ]); - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - ); - expect( - () async => rollDev( - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - usage: usage, - ), - throwsExceptionWith('Your git repository is not clean.'), - ); - }); - - test('does not reset or tag if --just-print is specified', () async { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - ]); - - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - justPrint: true, - ); - expect( - await rollDev( - usage: usage, - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - ), - false, - ); - expect(stdio.logs.join().contains(nextVersion), true); - }); - - test("exits with exception if --skip-tagging is provided but commit isn't already tagged", () { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - const FakeCommand(command: [ - 'git', - 'describe', - '--exact-match', - '--tags', - commit, - ], exitCode: 1), - ]); - - const String exceptionMessage = - 'The $kSkipTagging flag is only supported ' - 'for tagged commits.'; - - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - skipTagging: true, - ); - expect( - () async => rollDev( - usage: usage, - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - ), - throwsExceptionWith(exceptionMessage), - ); - }); - - test('throws exception if desired commit is already tip of dev branch', () { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - // [commit] is already [lastVersion] - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: commit), - ]); - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - justPrint: true, - ); - expect( - () async => rollDev( - usage: usage, - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - ), - throwsExceptionWith( - 'Commit $commit is already on the dev branch as $lastVersion', - ), - ); - }); - - test( - 'does not tag if last release is not direct ancestor of desired ' - 'commit and --force not supplied', () { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - const FakeCommand(command: [ - 'git', - 'merge-base', - '--is-ancestor', - lastVersion, - commit, - ], exitCode: 1), - ]); - - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - ); - const String errorMessage = 'The previous dev tag $lastVersion is not a ' - 'direct ancestor of $commit.'; - expect( - () async => rollDev( - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - usage: usage, - ), - throwsExceptionWith(errorMessage), - ); - }); - - test('does not tag but updates branch if --skip-tagging provided', () async { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - const FakeCommand(command: [ - 'git', - 'describe', - '--exact-match', - '--tags', - commit, - ]), - const FakeCommand(command: [ - 'git', - 'merge-base', - '--is-ancestor', - lastVersion, - commit, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - commit, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'push', - remote, - '$commit:dev', - ]), - ]); - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - skipTagging: true, - ); - expect( - await rollDev( - usage: usage, - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - ), - true, - ); - }); - - test('successfully tags and publishes release', () async { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - const FakeCommand(command: [ - 'git', - 'merge-base', - '--is-ancestor', - lastVersion, - commit, - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - commit, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'tag', - nextVersion, - commit, - ]), - const FakeCommand(command: [ - 'git', - 'push', - remote, - nextVersion, - ]), - const FakeCommand(command: [ - 'git', - 'push', - remote, - '$commit:dev', - ]), - ]); - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - ); - expect( - await rollDev( - usage: usage, - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - ), - true, - ); - }); - - test('successfully publishes release with --force', () async { - processManager.addCommands([ - const FakeCommand(command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - _kUpstreamRemote, - '${checkoutsParentDirectory}flutter_conductor_checkouts/framework', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - 'HEAD', - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'remote', - 'get-url', - remote, - ], stdout: _kUpstreamRemote), - const FakeCommand(command: [ - 'git', - 'status', - '--porcelain', - ]), - const FakeCommand(command: [ - 'git', - 'fetch', - remote, - '--tags', - ]), - const FakeCommand(command: [ - 'git', - 'rev-parse', - candidateBranch, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--exact-match', - '--tags', - 'refs/remotes/$remote/dev', - ], stdout: lastVersion), - const FakeCommand(command: [ - 'git', - 'rev-parse', - lastVersion, - ], stdout: 'zxy321'), - const FakeCommand(command: [ - 'git', - 'rev-parse', - commit, - ], stdout: commit), - const FakeCommand(command: [ - 'git', - 'tag', - nextVersion, - commit, - ]), - const FakeCommand(command: [ - 'git', - 'push', - remote, - nextVersion, - ]), - const FakeCommand(command: [ - 'git', - 'push', - '--force', - remote, - '$commit:dev', - ]), - ]); - - fakeArgResults = FakeArgResults( - level: level, - candidateBranch: candidateBranch, - remote: remote, - force: true, - ); - expect( - await rollDev( - argResults: fakeArgResults, - repository: repo, - stdio: stdio, - usage: usage, - ), - true, - ); - expect(processManager.hasRemainingExpectations, false); - }); - }, onPlatform: { - 'windows': const Skip('Flutter Conductor only supported on macos/linux'), - }); -} diff --git a/dev/conductor/core/test/start_test.dart b/dev/conductor/core/test/start_test.dart index 431ce2983cf..255aa886d36 100644 --- a/dev/conductor/core/test/start_test.dart +++ b/dev/conductor/core/test/start_test.dart @@ -163,7 +163,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], @@ -220,7 +220,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], @@ -348,7 +348,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], @@ -405,7 +405,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], @@ -541,7 +541,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'], @@ -598,7 +598,7 @@ void main() { command: ['git', 'fetch', 'mirror'], ), const FakeCommand( - command: ['git', 'checkout', 'upstream/$candidateBranch'], + command: ['git', 'checkout', candidateBranch], ), const FakeCommand( command: ['git', 'rev-parse', 'HEAD'],