| // 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 '../base/common.dart'; |
| import '../cache.dart'; |
| import '../globals.dart' as globals; |
| import '../runner/flutter_command.dart'; |
| import '../version.dart'; |
| |
| class ChannelCommand extends FlutterCommand { |
| ChannelCommand({ bool verboseHelp = false }) { |
| argParser.addFlag( |
| 'all', |
| abbr: 'a', |
| help: 'Include all the available branches (including local branches) when listing channels.', |
| hide: !verboseHelp, |
| ); |
| } |
| |
| @override |
| final String name = 'channel'; |
| |
| @override |
| final String description = 'List or switch Flutter channels.'; |
| |
| @override |
| final String category = FlutterCommandCategory.sdk; |
| |
| @override |
| String get invocation => '${runner?.executableName} $name [<channel-name>]'; |
| |
| @override |
| Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{}; |
| |
| @override |
| Future<FlutterCommandResult> runCommand() async { |
| final List<String> rest = argResults?.rest ?? <String>[]; |
| switch (rest.length) { |
| case 0: |
| await _listChannels( |
| showAll: boolArg('all'), |
| verbose: globalResults?['verbose'] == true, |
| ); |
| return FlutterCommandResult.success(); |
| case 1: |
| await _switchChannel(rest[0]); |
| return FlutterCommandResult.success(); |
| default: |
| throw ToolExit('Too many arguments.\n$usage'); |
| } |
| } |
| |
| Future<void> _listChannels({ required bool showAll, required bool verbose }) async { |
| // Beware: currentBranch could contain PII. See getBranchName(). |
| final String currentChannel = globals.flutterVersion.channel; |
| final String currentBranch = globals.flutterVersion.getBranchName(); |
| final Set<String> seenUnofficialChannels = <String>{}; |
| final List<String> rawOutput = <String>[]; |
| |
| showAll = showAll || currentChannel != currentBranch; |
| |
| globals.printStatus('Flutter channels:'); |
| final int result = await globals.processUtils.stream( |
| <String>['git', 'branch', '-r'], |
| workingDirectory: Cache.flutterRoot, |
| mapFunction: (String line) { |
| rawOutput.add(line); |
| return null; |
| }, |
| ); |
| if (result != 0) { |
| final String details = verbose ? '\n${rawOutput.join('\n')}' : ''; |
| throwToolExit('List channels failed: $result$details', exitCode: result); |
| } |
| |
| final List<String> officialChannels = kOfficialChannels.toList(); |
| final List<bool> availableChannels = List<bool>.filled(officialChannels.length, false); |
| |
| for (final String line in rawOutput) { |
| final List<String> split = line.split('/'); |
| if (split.length != 2) { |
| // We don't know how to parse this line, skip it. |
| continue; |
| } |
| final String branch = split[1]; |
| if (split.length > 1) { |
| final int index = officialChannels.indexOf(branch); |
| |
| if (index != -1) { // Mark all available channels official channels from output |
| availableChannels[index] = true; |
| } else if (showAll && !seenUnofficialChannels.contains(branch)) { |
| // add other branches to seenUnofficialChannels if --all flag is given (to print later) |
| seenUnofficialChannels.add(branch); |
| } |
| } |
| } |
| |
| // print all available official channels in sorted manner |
| for (int i = 0; i < officialChannels.length; i++) { |
| // only print non-missing channels |
| if (availableChannels[i]) { |
| String currentIndicator = ' '; |
| if (officialChannels[i] == currentChannel) { |
| currentIndicator = '*'; |
| } |
| globals.printStatus('$currentIndicator ${officialChannels[i]}'); |
| } |
| } |
| |
| // print all remaining channels if showAll is true |
| if (showAll) { |
| for (final String branch in seenUnofficialChannels) { |
| if (currentBranch == branch) { |
| globals.printStatus('* $branch'); |
| } else if (!branch.startsWith('HEAD ')) { |
| globals.printStatus(' $branch'); |
| } |
| } |
| } |
| |
| if (currentChannel == 'unknown') { |
| globals.printStatus(''); |
| globals.printStatus('Currently not on an official channel.'); |
| } |
| } |
| |
| Future<void> _switchChannel(String branchName) async { |
| globals.printStatus("Switching to flutter channel '$branchName'..."); |
| if (kObsoleteBranches.containsKey(branchName)) { |
| final String alternative = kObsoleteBranches[branchName]!; |
| globals.printStatus("This channel is obsolete. Consider switching to the '$alternative' channel instead."); |
| } else if (!kOfficialChannels.contains(branchName)) { |
| globals.printStatus('This is not an official channel. For a list of available channels, try "flutter channel".'); |
| } |
| await _checkout(branchName); |
| globals.printStatus("Successfully switched to flutter channel '$branchName'."); |
| globals.printStatus("To ensure that you're on the latest build from this channel, run 'flutter upgrade'"); |
| } |
| |
| static Future<void> upgradeChannel(FlutterVersion currentVersion) async { |
| final String channel = currentVersion.channel; |
| if (kObsoleteBranches.containsKey(channel)) { |
| final String alternative = kObsoleteBranches[channel]!; |
| globals.printStatus("Transitioning from '$channel' to '$alternative'..."); |
| return _checkout(alternative); |
| } |
| } |
| |
| static Future<void> _checkout(String branchName) async { |
| // Get latest refs from upstream. |
| int result = await globals.processUtils.stream( |
| <String>['git', 'fetch'], |
| workingDirectory: Cache.flutterRoot, |
| prefix: 'git: ', |
| ); |
| |
| if (result == 0) { |
| result = await globals.processUtils.stream( |
| <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'], |
| workingDirectory: Cache.flutterRoot, |
| prefix: 'git: ', |
| ); |
| if (result == 0) { |
| // branch already exists, try just switching to it |
| result = await globals.processUtils.stream( |
| <String>['git', 'checkout', branchName, '--'], |
| workingDirectory: Cache.flutterRoot, |
| prefix: 'git: ', |
| ); |
| } else { |
| // branch does not exist, we have to create it |
| result = await globals.processUtils.stream( |
| <String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'], |
| workingDirectory: Cache.flutterRoot, |
| prefix: 'git: ', |
| ); |
| } |
| } |
| if (result != 0) { |
| throwToolExit('Switching channels failed with error code $result.', exitCode: result); |
| } else { |
| // Remove the version check stamp, since it could contain out-of-date |
| // information that pertains to the previous channel. |
| await FlutterVersion.resetFlutterVersionFreshnessCheck(); |
| } |
| } |
| } |