1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import '../base/common.dart'; 8import '../base/process.dart'; 9import '../cache.dart'; 10import '../globals.dart'; 11import '../runner/flutter_command.dart'; 12import '../version.dart'; 13 14class ChannelCommand extends FlutterCommand { 15 ChannelCommand({ bool verboseHelp = false }) { 16 argParser.addFlag( 17 'all', 18 abbr: 'a', 19 help: 'Include all the available branches (including local branches) when listing channels.', 20 defaultsTo: false, 21 hide: !verboseHelp, 22 ); 23 } 24 25 @override 26 final String name = 'channel'; 27 28 @override 29 final String description = 'List or switch flutter channels.'; 30 31 @override 32 String get invocation => '${runner.executableName} $name [<channel-name>]'; 33 34 @override 35 Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{}; 36 37 @override 38 Future<FlutterCommandResult> runCommand() async { 39 switch (argResults.rest.length) { 40 case 0: 41 await _listChannels( 42 showAll: argResults['all'], 43 verbose: globalResults['verbose'], 44 ); 45 return null; 46 case 1: 47 await _switchChannel(argResults.rest[0]); 48 return null; 49 default: 50 throw ToolExit('Too many arguments.\n$usage'); 51 } 52 } 53 54 Future<void> _listChannels({ bool showAll, bool verbose }) async { 55 // Beware: currentBranch could contain PII. See getBranchName(). 56 final String currentChannel = FlutterVersion.instance.channel; 57 final String currentBranch = FlutterVersion.instance.getBranchName(); 58 final Set<String> seenChannels = <String>{}; 59 final List<String> rawOutput = <String>[]; 60 61 showAll = showAll || currentChannel != currentBranch; 62 63 printStatus('Flutter channels:'); 64 final int result = await runCommandAndStreamOutput( 65 <String>['git', 'branch', '-r'], 66 workingDirectory: Cache.flutterRoot, 67 mapFunction: (String line) { 68 if (verbose) 69 rawOutput.add(line); 70 final List<String> split = line.split('/'); 71 if (split.length < 2) 72 return null; 73 final String branchName = split[1]; 74 if (seenChannels.contains(branchName)) { 75 return null; 76 } 77 seenChannels.add(branchName); 78 if (branchName == currentBranch) 79 return '* $branchName'; 80 if (!branchName.startsWith('HEAD ') && 81 (showAll || FlutterVersion.officialChannels.contains(branchName))) 82 return ' $branchName'; 83 return null; 84 }, 85 ); 86 if (result != 0) { 87 final String details = verbose ? '\n${rawOutput.join('\n')}' : ''; 88 throwToolExit('List channels failed: $result$details', exitCode: result); 89 } 90 } 91 92 Future<void> _switchChannel(String branchName) { 93 printStatus("Switching to flutter channel '$branchName'..."); 94 if (FlutterVersion.obsoleteBranches.containsKey(branchName)) { 95 final String alternative = FlutterVersion.obsoleteBranches[branchName]; 96 printStatus("This channel is obsolete. Consider switching to the '$alternative' channel instead."); 97 } else if (!FlutterVersion.officialChannels.contains(branchName)) { 98 printStatus('This is not an official channel. For a list of available channels, try "flutter channel".'); 99 } 100 return _checkout(branchName); 101 } 102 103 static Future<void> upgradeChannel() async { 104 final String channel = FlutterVersion.instance.channel; 105 if (FlutterVersion.obsoleteBranches.containsKey(channel)) { 106 final String alternative = FlutterVersion.obsoleteBranches[channel]; 107 printStatus("Transitioning from '$channel' to '$alternative'..."); 108 return _checkout(alternative); 109 } 110 } 111 112 static Future<void> _checkout(String branchName) async { 113 // Get latest refs from upstream. 114 int result = await runCommandAndStreamOutput( 115 <String>['git', 'fetch'], 116 workingDirectory: Cache.flutterRoot, 117 prefix: 'git: ', 118 ); 119 120 if (result == 0) { 121 result = await runCommandAndStreamOutput( 122 <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'], 123 workingDirectory: Cache.flutterRoot, 124 prefix: 'git: ', 125 ); 126 if (result == 0) { 127 // branch already exists, try just switching to it 128 result = await runCommandAndStreamOutput( 129 <String>['git', 'checkout', branchName, '--'], 130 workingDirectory: Cache.flutterRoot, 131 prefix: 'git: ', 132 ); 133 } else { 134 // branch does not exist, we have to create it 135 result = await runCommandAndStreamOutput( 136 <String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'], 137 workingDirectory: Cache.flutterRoot, 138 prefix: 'git: ', 139 ); 140 } 141 } 142 if (result != 0) { 143 throwToolExit('Switching channels failed with error code $result.', exitCode: result); 144 } else { 145 // Remove the version check stamp, since it could contain out-of-date 146 // information that pertains to the previous channel. 147 await FlutterVersion.resetFlutterVersionFreshnessCheck(); 148 } 149 } 150} 151