| // 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<void> { |
| 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: <String>['y', 'z', 'm'], |
| allowedHelp: <String, String>{ |
| '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', |
| defaultsTo: null, // This option is required |
| ); |
| 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 |
| void run() { |
| rollDev( |
| argResults: argResults!, |
| repository: FrameworkRepository(checkouts), |
| stdio: stdio, |
| usage: argParser.usage, |
| ); |
| } |
| } |
| |
| /// Main script execution. |
| /// |
| /// Returns true if publishing was successful, else false. |
| @visibleForTesting |
| bool rollDev({ |
| required String usage, |
| required ArgResults argResults, |
| required Stdio stdio, |
| required FrameworkRepository repository, |
| }) { |
| 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 = repository.remoteUrl(remoteName); |
| |
| if (!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.'); |
| } |
| |
| repository.fetch(remoteName); |
| |
| // Verify [commit] is valid |
| final String commit = repository.reverseParse(candidateBranch); |
| |
| stdio.printStatus('remoteName is $remoteName'); |
| // Get the name of the last dev release |
| final Version lastVersion = Version.fromString( |
| repository.getFullTag(remoteName, 'dev'), |
| ); |
| |
| final Version version = |
| skipTagging ? lastVersion : Version.fromCandidateBranch(candidateBranch); |
| final String tagName = version.toString(); |
| |
| if (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 && !repository.isCommitTagged(commit)) { |
| throw Exception( |
| 'The $kSkipTagging flag is only supported for tagged commits.'); |
| } |
| |
| if (!force && !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 = 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) { |
| repository.tag(commit, version.toString(), remoteName); |
| } |
| |
| 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; |
| } |