Skip to content

Commit 9c7a9e7

Browse files
authored
Give channel descriptions in flutter channel, use branch instead of upstream for channel name (#126936)
## How we determine the channel name Historically, we used the current branch's upstream to figure out the current channel name. I have no idea why. I traced it back to https://github.com/flutter/flutter/pull/446/files where @abarth implement this and I reviewed that PR and left no comment on it at the time. I think this is confusing. You can be on a branch and it tells you that your channel is different. That seems weird. This PR changes the logic to uses the current branch as the channel name. ## How we display channels The main reason this PR exists is to add channel descriptions to the `flutter channel` list: ``` ianh@burmese:~/dev/flutter/packages/flutter_tools$ flutter channel Flutter channels: master (tip of tree, for contributors) main (tip of tree, follows master channel) beta (updated monthly, recommended for experienced users) stable (updated quarterly, for new users and for production app releases) * foo_bar Currently not on an official channel. ianh@burmese:~/dev/flutter/packages/flutter_tools$ ``` ## Other changes I made a few other changes while I was at it: * If you're not on an official channel, we used to imply `--show-all`, but now we don't, we just show the official channels plus yours. This avoids flooding the screen in the case the user is on a weird channel and just wants to know what channel they're on. * I made the tool more consistent about how it handles unofficial branches. Now it's always `[user branch]`. * I slightly adjusted how unknown versions are rendered so it's clearer the version is unknown rather than just having the word "Unknown" floating in the output without context. * Simplified some of the code. * Made some of the tests more strict (checking all output rather than just some aspects of it). * Changed the MockFlutterVersion to implement the FlutterVersion API more strictly. * I made sure we escape the output to `.metadata` to avoid potential injection bugs (previously we just inlined the version and channel name verbatim with no escaping, which is super sketchy). * Tweaked the help text for the `downgrade` command to be clearer. * Removed some misleading text in some error messages. * Made the `.metadata` generator consistent with the template file. * Removed some obsolete code to do with the `dev` branch. ## Reviewer notes I'm worried that there are implications to some of these changes that I am not aware of, so please don't assume I know what I'm doing when reviewing this code. :-)
1 parent 25d2f90 commit 9c7a9e7

20 files changed

+267
-189
lines changed

packages/flutter_tools/lib/src/base/user_messages.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class UserMessages {
2121

2222
// Messages used in FlutterValidator
2323
String flutterStatusInfo(String? channel, String? version, String os, String locale) =>
24-
'Channel ${channel ?? 'unknown'}, ${version ?? 'Unknown'}, on $os, locale $locale';
24+
'Channel ${channel ?? 'unknown'}, ${version ?? 'unknown version'}, on $os, locale $locale';
2525
String flutterVersion(String version, String channel, String flutterRoot) =>
2626
'Flutter version $version on channel $channel at $flutterRoot';
2727
String get flutterUnknownChannel =>

packages/flutter_tools/lib/src/commands/channel.dart

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,12 @@ class ChannelCommand extends FlutterCommand {
5353

5454
Future<void> _listChannels({ required bool showAll, required bool verbose }) async {
5555
// Beware: currentBranch could contain PII. See getBranchName().
56-
final String currentChannel = globals.flutterVersion.channel;
56+
final String currentChannel = globals.flutterVersion.channel; // limited to known branch names
57+
assert(kOfficialChannels.contains(currentChannel) || kObsoleteBranches.containsKey(currentChannel) || currentChannel == kUserBranch, 'potential PII leak in channel name: "$currentChannel"');
5758
final String currentBranch = globals.flutterVersion.getBranchName();
5859
final Set<String> seenUnofficialChannels = <String>{};
5960
final List<String> rawOutput = <String>[];
6061

61-
showAll = showAll || currentChannel != currentBranch;
62-
6362
globals.printStatus('Flutter channels:');
6463
final int result = await globals.processUtils.stream(
6564
<String>['git', 'branch', '-r'],
@@ -74,8 +73,7 @@ class ChannelCommand extends FlutterCommand {
7473
throwToolExit('List channels failed: $result$details', exitCode: result);
7574
}
7675

77-
final List<String> officialChannels = kOfficialChannels.toList();
78-
final List<bool> availableChannels = List<bool>.filled(officialChannels.length, false);
76+
final Set<String> availableChannels = <String>{};
7977

8078
for (final String line in rawOutput) {
8179
final List<String> split = line.split('/');
@@ -84,27 +82,25 @@ class ChannelCommand extends FlutterCommand {
8482
continue;
8583
}
8684
final String branch = split[1];
87-
if (split.length > 1) {
88-
final int index = officialChannels.indexOf(branch);
89-
90-
if (index != -1) { // Mark all available channels official channels from output
91-
availableChannels[index] = true;
92-
} else if (showAll && !seenUnofficialChannels.contains(branch)) {
93-
// add other branches to seenUnofficialChannels if --all flag is given (to print later)
94-
seenUnofficialChannels.add(branch);
95-
}
85+
if (kOfficialChannels.contains(branch)) {
86+
availableChannels.add(branch);
87+
} else if (showAll) {
88+
seenUnofficialChannels.add(branch);
9689
}
9790
}
9891

92+
bool currentChannelIsOfficial = false;
93+
9994
// print all available official channels in sorted manner
100-
for (int i = 0; i < officialChannels.length; i++) {
95+
for (final String channel in kOfficialChannels) {
10196
// only print non-missing channels
102-
if (availableChannels[i]) {
97+
if (availableChannels.contains(channel)) {
10398
String currentIndicator = ' ';
104-
if (officialChannels[i] == currentChannel) {
99+
if (channel == currentChannel) {
105100
currentIndicator = '*';
101+
currentChannelIsOfficial = true;
106102
}
107-
globals.printStatus('$currentIndicator ${officialChannels[i]}');
103+
globals.printStatus('$currentIndicator $channel (${kChannelDescriptions[channel]})');
108104
}
109105
}
110106

@@ -117,9 +113,12 @@ class ChannelCommand extends FlutterCommand {
117113
globals.printStatus(' $branch');
118114
}
119115
}
116+
} else if (!currentChannelIsOfficial) {
117+
globals.printStatus('* $currentBranch');
120118
}
121119

122-
if (currentChannel == 'unknown') {
120+
if (!currentChannelIsOfficial) {
121+
assert(currentChannel == kUserBranch, 'Current channel is "$currentChannel", which is not an official branch. (Current branch is "$currentBranch".)');
123122
globals.printStatus('');
124123
globals.printStatus('Currently not on an official channel.');
125124
}

packages/flutter_tools/lib/src/commands/create_base.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,8 @@ abstract class CreateBase extends FlutterCommand {
407407
'iosLanguage': iosLanguage,
408408
'hasIosDevelopmentTeam': iosDevelopmentTeam != null && iosDevelopmentTeam.isNotEmpty,
409409
'iosDevelopmentTeam': iosDevelopmentTeam ?? '',
410-
'flutterRevision': globals.flutterVersion.frameworkRevision,
411-
'flutterChannel': globals.flutterVersion.channel,
410+
'flutterRevision': escapeYamlString(globals.flutterVersion.frameworkRevision),
411+
'flutterChannel': escapeYamlString(globals.flutterVersion.getBranchName()), // may contain PII
412412
'ios': ios,
413413
'android': android,
414414
'web': web,
@@ -571,10 +571,11 @@ abstract class CreateBase extends FlutterCommand {
571571
final FlutterProjectMetadata metadata = FlutterProjectMetadata.explicit(
572572
file: metadataFile,
573573
versionRevision: globals.flutterVersion.frameworkRevision,
574-
versionChannel: globals.flutterVersion.channel,
574+
versionChannel: globals.flutterVersion.getBranchName(), // may contain PII
575575
projectType: projectType,
576576
migrateConfig: MigrateConfig(),
577-
logger: globals.logger);
577+
logger: globals.logger,
578+
);
578579
metadata.populate(
579580
platforms: platformsForMigrateConfig,
580581
projectDirectory: directory,

packages/flutter_tools/lib/src/commands/downgrade.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ class DowngradeCommand extends FlutterCommand {
4747
'working-directory',
4848
hide: !verboseHelp,
4949
help: 'Override the downgrade working directory. '
50-
'This is only intended to enable integration testing of the tool itself.'
50+
'This is only intended to enable integration testing of the tool itself. '
51+
'It allows one to use the flutter tool from one checkout to downgrade a '
52+
'different checkout.'
5153
);
5254
argParser.addFlag(
5355
'prompt',
5456
defaultsTo: true,
5557
hide: !verboseHelp,
56-
help: 'Show the downgrade prompt. '
57-
'The ability to disable this using "--no-prompt" is only provided for '
58-
'integration testing of the tool itself.'
58+
help: 'Show the downgrade prompt.'
5959
);
6060
}
6161

@@ -99,8 +99,8 @@ class DowngradeCommand extends FlutterCommand {
9999
final Channel? channel = getChannelForName(currentChannel);
100100
if (channel == null) {
101101
throwToolExit(
102-
'Flutter is not currently on a known channel. Use "flutter channel <name>" '
103-
'to switch to an official channel.',
102+
'Flutter is not currently on a known channel. '
103+
'Use "flutter channel" to switch to an official channel. '
104104
);
105105
}
106106
final PersistentToolState persistentToolState = _persistentToolState!;
@@ -153,23 +153,23 @@ class DowngradeCommand extends FlutterCommand {
153153
} on ProcessException catch (error) {
154154
throwToolExit(
155155
'Unable to downgrade Flutter: The tool could not update to the version '
156-
'$humanReadableVersion. This may be due to git not being installed or an '
157-
'internal error. Please ensure that git is installed on your computer and '
158-
'retry again.\nError: $error.'
156+
'$humanReadableVersion.\n'
157+
'Error: $error'
159158
);
160159
}
161160
try {
162161
await processUtils.run(
162+
// The `--` bit (because it's followed by nothing) means that we don't actually change
163+
// anything in the working tree, which avoids the need to first go into detached HEAD mode.
163164
<String>['git', 'checkout', currentChannel, '--'],
164165
throwOnError: true,
165166
workingDirectory: workingDirectory,
166167
);
167168
} on ProcessException catch (error) {
168169
throwToolExit(
169170
'Unable to downgrade Flutter: The tool could not switch to the channel '
170-
'$currentChannel. This may be due to git not being installed or an '
171-
'internal error. Please ensure that git is installed on your computer '
172-
'and retry again.\nError: $error.'
171+
'$currentChannel.\n'
172+
'Error: $error'
173173
);
174174
}
175175
await FlutterVersion.resetFlutterVersionFreshnessCheck();

packages/flutter_tools/lib/src/doctor.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -577,14 +577,14 @@ class FlutterValidator extends DoctorValidator {
577577
ValidationMessage _getFlutterVersionMessage(String frameworkVersion, String versionChannel, String flutterRoot) {
578578
String flutterVersionMessage = _userMessages.flutterVersion(frameworkVersion, versionChannel, flutterRoot);
579579

580-
// The tool sets the channel as "unknown", if the current branch is on a
581-
// "detached HEAD" state or doesn't have an upstream, and sets the
582-
// frameworkVersion as "0.0.0-unknown" if "git describe" on HEAD doesn't
583-
// produce an expected format to be parsed for the frameworkVersion.
584-
if (versionChannel != 'unknown' && frameworkVersion != '0.0.0-unknown') {
580+
// The tool sets the channel as kUserBranch, if the current branch is on a
581+
// "detached HEAD" state, doesn't have an upstream, or is on a user branch,
582+
// and sets the frameworkVersion as "0.0.0-unknown" if "git describe" on
583+
// HEAD doesn't produce an expected format to be parsed for the frameworkVersion.
584+
if (versionChannel != kUserBranch && frameworkVersion != '0.0.0-unknown') {
585585
return ValidationMessage(flutterVersionMessage);
586586
}
587-
if (versionChannel == 'unknown') {
587+
if (versionChannel == kUserBranch) {
588588
flutterVersionMessage = '$flutterVersionMessage\n${_userMessages.flutterUnknownChannel}';
589589
}
590590
if (frameworkVersion == '0.0.0-unknown') {

packages/flutter_tools/lib/src/flutter_project_metadata.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'base/file_system.dart';
88
import 'base/logger.dart';
99
import 'base/utils.dart';
1010
import 'project.dart';
11+
import 'template.dart';
1112
import 'version.dart';
1213

1314
enum FlutterProjectType implements CliEnum {
@@ -172,11 +173,11 @@ class FlutterProjectMetadata {
172173
# This file tracks properties of this Flutter project.
173174
# Used by Flutter tool to assess capabilities and perform upgrades etc.
174175
#
175-
# This file should be version controlled.
176+
# This file should be version controlled and should not be manually edited.
176177
177178
version:
178-
revision: $_versionRevision
179-
channel: $_versionChannel
179+
revision: ${escapeYamlString(_versionRevision ?? '')}
180+
channel: ${escapeYamlString(_versionChannel ?? kUserBranch)}
180181
181182
project_type: ${projectType == null ? '' : projectType!.cliName}
182183
${migrateConfig.getOutputFileString()}''';

packages/flutter_tools/lib/src/globals.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,6 @@ CustomDevicesConfig get customDevicesConfig => context.get<CustomDevicesConfig>(
300300

301301
PreRunValidator get preRunValidator => context.get<PreRunValidator>() ?? const NoOpPreRunValidator();
302302

303-
// TODO(fujino): Migrate to 'main' https://github.com/flutter/flutter/issues/95041
304-
const String kDefaultFrameworkChannel = 'master';
305-
306303
// Used to build RegExp instances which can detect the VM service message.
307304
final RegExp kVMServiceMessageRegExp = RegExp(r'The Dart VM service is listening on ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
308305

packages/flutter_tools/lib/src/template.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,24 @@ String _escapeKotlinKeywords(String androidIdentifier) {
375375
).toList();
376376
return correctedSegments.join('.');
377377
}
378+
379+
String escapeYamlString(String value) {
380+
final StringBuffer result = StringBuffer();
381+
result.write('"');
382+
for (final int rune in value.runes) {
383+
result.write(
384+
switch (rune) {
385+
0x00 => r'\0',
386+
0x09 => r'\t',
387+
0x0A => r'\n',
388+
0x0D => r'\r',
389+
0x22 => r'\"',
390+
0x5C => r'\\',
391+
< 0x20 => '\\x${rune.toRadixString(16).padLeft(2, "0")}',
392+
_ => String.fromCharCode(rune),
393+
}
394+
);
395+
}
396+
result.write('"');
397+
return result.toString();
398+
}

packages/flutter_tools/lib/src/version.dart

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const String _unknownFrameworkVersion = '0.0.0-unknown';
2222
/// See `man gitrevisions` for more information.
2323
const String kGitTrackingUpstream = '@{upstream}';
2424

25+
/// Replacement name when the branch is user-specific.
26+
const String kUserBranch = '[user-branch]';
27+
2528
/// This maps old branch names to the names of branches that replaced them.
2629
///
2730
/// For example, in 2021 we deprecated the "dev" channel and transitioned "dev"
@@ -40,12 +43,24 @@ enum Channel {
4043

4144
// Beware: Keep order in accordance with stability
4245
const Set<String> kOfficialChannels = <String>{
43-
globals.kDefaultFrameworkChannel,
46+
'master',
4447
'main',
4548
'beta',
4649
'stable',
4750
};
4851

52+
const Map<String, String> kChannelDescriptions = <String, String>{
53+
'master': 'latest development branch, for contributors',
54+
'main': 'latest development branch, follows master channel',
55+
'beta': 'updated monthly, recommended for experienced users',
56+
'stable': 'updated quarterly, for new users and for production app releases',
57+
};
58+
59+
const Set<String> kDevelopmentChannels = <String>{
60+
'master',
61+
'main',
62+
};
63+
4964
/// Retrieve a human-readable name for a given [channel].
5065
///
5166
/// Requires [kOfficialChannels] to be correctly ordered.
@@ -101,16 +116,7 @@ class FlutterVersion {
101116

102117
String? _repositoryUrl;
103118
String? get repositoryUrl {
104-
final String _ = channel;
105-
return _repositoryUrl;
106-
}
107-
108-
String? _channel;
109-
/// The channel is the upstream branch.
110-
/// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ...
111-
String get channel {
112-
String? channel = _channel;
113-
if (channel == null) {
119+
if (_repositoryUrl == null) {
114120
final String gitChannel = _runGit(
115121
'git rev-parse --abbrev-ref --symbolic $kGitTrackingUpstream',
116122
globals.processUtils,
@@ -124,14 +130,16 @@ class FlutterVersion {
124130
globals.processUtils,
125131
_workingDirectory,
126132
);
127-
channel = gitChannel.substring(slash + 1);
128-
} else if (gitChannel.isEmpty) {
129-
channel = 'unknown';
130-
} else {
131-
channel = gitChannel;
132133
}
133-
_channel = channel;
134134
}
135+
return _repositoryUrl;
136+
}
137+
138+
/// The channel is the current branch if we recognize it, or "[user-branch]" (kUserBranch).
139+
/// `master`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, `dev`, ...
140+
String get channel {
141+
final String channel = getBranchName(redactUnknownBranches: true);
142+
assert(kOfficialChannels.contains(channel) || kObsoleteBranches.containsKey(channel) || channel == kUserBranch, 'Potential PII leak in channel name: "$channel"');
135143
return channel;
136144
}
137145

@@ -296,16 +304,16 @@ class FlutterVersion {
296304
/// Return the branch name.
297305
///
298306
/// If [redactUnknownBranches] is true and the branch is unknown,
299-
/// the branch name will be returned as `'[user-branch]'`.
307+
/// the branch name will be returned as `'[user-branch]'` ([kUserBranch]).
300308
String getBranchName({ bool redactUnknownBranches = false }) {
301309
_branch ??= () {
302-
final String branch = _runGit('git rev-parse --abbrev-ref HEAD', globals.processUtils);
303-
return branch == 'HEAD' ? channel : branch;
310+
final String branch = _runGit('git symbolic-ref --short HEAD', globals.processUtils, _workingDirectory);
311+
return branch == 'HEAD' ? '' : branch;
304312
}();
305313
if (redactUnknownBranches || _branch!.isEmpty) {
306314
// Only return the branch names we know about; arbitrary branch names might contain PII.
307315
if (!kOfficialChannels.contains(_branch) && !kObsoleteBranches.containsKey(_branch)) {
308-
return '[user-branch]';
316+
return kUserBranch;
309317
}
310318
}
311319
return _branch!;
@@ -619,7 +627,7 @@ String _runSync(List<String> command, { bool lenient = true }) {
619627
return '';
620628
}
621629

622-
String _runGit(String command, ProcessUtils processUtils, [String? workingDirectory]) {
630+
String _runGit(String command, ProcessUtils processUtils, String? workingDirectory) {
623631
return processUtils.runSync(
624632
command.split(' '),
625633
workingDirectory: workingDirectory ?? Cache.flutterRoot,
@@ -709,8 +717,8 @@ class GitTagVersion {
709717
String gitRef = 'HEAD'
710718
}) {
711719
if (fetchTags) {
712-
final String channel = _runGit('git rev-parse --abbrev-ref HEAD', processUtils, workingDirectory);
713-
if (channel == 'dev' || channel == 'beta' || channel == 'stable') {
720+
final String channel = _runGit('git symbolic-ref --short HEAD', processUtils, workingDirectory);
721+
if (!kDevelopmentChannels.contains(channel) && kOfficialChannels.contains(channel)) {
714722
globals.printTrace('Skipping request to fetchTags - on well known channel $channel.');
715723
} else {
716724
final String flutterGit = platform.environment['FLUTTER_GIT_URL'] ?? 'https://github.com/flutter/flutter.git';
@@ -918,8 +926,6 @@ class VersionFreshnessValidator {
918926
return const Duration(days: 365 ~/ 2); // Six months
919927
case 'beta':
920928
return const Duration(days: 7 * 8); // Eight weeks
921-
case 'dev':
922-
return const Duration(days: 7 * 4); // Four weeks
923929
default:
924930
return const Duration(days: 7 * 3); // Three weeks
925931
}

0 commit comments

Comments
 (0)