diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart index c100112c3c3..e0daef10a48 100644 --- a/packages/flutter_tools/lib/src/commands/channel.dart +++ b/packages/flutter_tools/lib/src/commands/channel.dart @@ -123,7 +123,10 @@ class ChannelCommand extends FlutterCommand { Future _switchChannel(String branchName) async { globals.printStatus("Switching to flutter channel '$branchName'..."); - if (!kOfficialChannels.contains(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); @@ -131,6 +134,15 @@ class ChannelCommand extends FlutterCommand { globals.printStatus("To ensure that you're on the latest build from this channel, run 'flutter upgrade'"); } + static Future 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 _checkout(String branchName) async { // Get latest refs from upstream. int result = await globals.processUtils.stream( diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index 3ed0cff563a..788844e946b 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -14,6 +14,7 @@ import '../globals.dart' as globals; import '../persistent_tool_state.dart'; import '../runner/flutter_command.dart'; import '../version.dart'; +import 'channel.dart'; // The official docs to install Flutter. const String _flutterInstallDocs = 'https://flutter.dev/docs/get-started/install'; @@ -163,6 +164,7 @@ class UpgradeCommandRunner { ); } recordState(flutterVersion); + await ChannelCommand.upgradeChannel(flutterVersion); globals.printStatus('Upgrading Flutter to ${upstreamVersion.frameworkVersion} from ${flutterVersion.frameworkVersion} in $workingDirectory...'); await attemptReset(upstreamVersion.frameworkRevision); if (!testFlow) { diff --git a/packages/flutter_tools/lib/src/persistent_tool_state.dart b/packages/flutter_tools/lib/src/persistent_tool_state.dart index cdccc3bbac4..34ff2135259 100644 --- a/packages/flutter_tools/lib/src/persistent_tool_state.dart +++ b/packages/flutter_tools/lib/src/persistent_tool_state.dart @@ -83,7 +83,6 @@ class _DefaultPersistentToolState implements PersistentToolState { static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message'; static const Map _lastActiveVersionKeys = { Channel.master: 'last-active-master-version', - Channel.dev: 'last-active-dev-version', Channel.beta: 'last-active-beta-version', Channel.stable: 'last-active-stable-version' }; diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index aeb3de5973a..03546c06a9a 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -16,11 +16,18 @@ import 'globals.dart' as globals; const String _unknownFrameworkVersion = '0.0.0-unknown'; +/// This maps old branch names to the names of branches that replaced them. +/// +/// For example, in 2021 we deprecated the "dev" channel and transitioned "dev" +/// users to the "beta" channel. +const Map kObsoleteBranches = { + 'dev': 'beta', +}; + /// The names of each channel/branch in order of increasing stability. enum Channel { // TODO(fujino): update to main https://github.com/flutter/flutter/issues/95041 master, - dev, beta, stable, } @@ -28,7 +35,6 @@ enum Channel { // Beware: Keep order in accordance with stability const Set kOfficialChannels = { globals.kDefaultFrameworkChannel, - 'dev', 'beta', 'stable', }; @@ -320,7 +326,7 @@ class FlutterVersion { }(); if (redactUnknownBranches || _branch!.isEmpty) { // Only return the branch names we know about; arbitrary branch names might contain PII. - if (!kOfficialChannels.contains(_branch)) { + if (!kOfficialChannels.contains(_branch) && !kObsoleteBranches.containsKey(_branch)) { return '[user-branch]'; } } @@ -634,6 +640,7 @@ class GitTagVersion { _runGit('git fetch ${globals.flutterGit} --tags -f', processUtils, workingDirectory); } } + // find all tags attached to the given [gitRef] final List tags = _runGit( 'git tag --points-at $gitRef', processUtils, workingDirectory).trim().split('\n'); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/downgrade_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/downgrade_test.dart index 3f0c113bbe2..127744eaa1f 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/downgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/downgrade_test.dart @@ -60,7 +60,7 @@ void main() { }); testUsingContext('Downgrade exits on no recorded version', () async { - final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'dev'); + final FakeFlutterVersion fakeFlutterVersion = FakeFlutterVersion(channel: 'beta'); fileSystem.currentDirectory.childFile('.flutter_tool_state') .writeAsStringSync('{"last-active-master-version":"abcd"}'); final DowngradeCommand command = DowngradeCommand( @@ -81,7 +81,7 @@ void main() { expect(createTestCommandRunner(command).run(const ['downgrade']), throwsToolExit(message: - 'There is no previously recorded version for channel "dev".\n' + 'There is no previously recorded version for channel "beta".\n' 'Channel "master" was previously on: v1.2.3.' ), ); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart new file mode 100644 index 00000000000..b758713ad93 --- /dev/null +++ b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart @@ -0,0 +1,126 @@ +// 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. + +// @dart = 2.8 + +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/cache.dart'; +import 'package:flutter_tools/src/commands/upgrade.dart'; +import 'package:flutter_tools/src/version.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/fake_process_manager.dart'; +import '../../src/fakes.dart' show FakeFlutterVersion; +import '../../src/test_flutter_command_runner.dart'; + +void main() { + FileSystem fileSystem; + BufferLogger logger; + FakeProcessManager processManager; + UpgradeCommand command; + CommandRunner runner; + FlutterVersion flutterVersion; + + setUpAll(() { + Cache.disableLocking(); + }); + + setUp(() { + fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); + processManager = FakeProcessManager.empty(); + command = UpgradeCommand( + verboseHelp: false, + ); + runner = createTestCommandRunner(command); + }); + + testUsingContext('can auto-migrate a user from dev to beta', () async { + const String startingTag = '3.0.0-1.2.pre'; + flutterVersion = FakeFlutterVersion(channel: 'dev'); + const String latestUpstreamTag = '3.0.0-1.3.pre'; + const String upstreamHeadRevision = 'deadbeef'; + final Completer reEntryCompleter = Completer(); + + Future reEnterTool() async { + await runner.run(['upgrade', '--continue', '--no-version-check']); + reEntryCompleter.complete(); + } + + processManager.addCommands([ + const FakeCommand( + command: ['git', 'tag', '--points-at', 'HEAD'], + stdout: startingTag, + ), + // Ensure we have upstream tags present locally + const FakeCommand( + command: ['git', 'fetch', '--tags'], + ), + const FakeCommand( + command: ['git', 'rev-parse', '--verify', '@{u}'], + stdout: upstreamHeadRevision, + ), + const FakeCommand( + command: ['git', 'tag', '--points-at', upstreamHeadRevision], + stdout: latestUpstreamTag, + ), + // check for uncommitted changes; empty stdout means clean checkout + const FakeCommand( + command: ['git', 'status', '-s'], + ), + + // here the tool is upgrading the branch from dev -> beta + const FakeCommand( + command: ['git', 'fetch'], + ), + // test if there already exists a local beta branch; 0 exit code means yes + const FakeCommand( + command: ['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], + ), + const FakeCommand( + command: ['git', 'checkout', 'beta', '--'], + ), + + // reset instead of pull since cherrypicks from one release branch will + // not be present on a newer one + const FakeCommand( + command: ['git', 'reset', '--hard', upstreamHeadRevision], + ), + // re-enter flutter command with the newer version, so that `doctor` + // checks will be up to date + FakeCommand( + command: const ['bin/flutter', 'upgrade', '--continue', '--no-version-check'], + onRun: reEnterTool, + completer: reEntryCompleter, + ), + + // commands following this are from the re-entrant `flutter upgrade --continue` call + + const FakeCommand( + command: ['git', 'tag', '--points-at', 'HEAD'], + stdout: latestUpstreamTag, + ), + const FakeCommand( + command: ['bin/flutter', '--no-color', '--no-version-check', 'precache'], + ), + const FakeCommand( + command: ['bin/flutter', '--no-version-check', 'doctor'], + ), + ]); + await runner.run(['upgrade']); + expect(processManager, hasNoRemainingExpectations); + expect(logger.statusText, contains("Transitioning from 'dev' to 'beta'...")); + }, overrides: { + FileSystem: () => fileSystem, + FlutterVersion: () => flutterVersion, + Logger: () => logger, + ProcessManager: () => processManager, + }); +} diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index 4821d907100..010294952e8 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -48,7 +48,7 @@ void main() { }); testUsingContext('throws on unknown tag, official branch, noforce', () async { - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev'); + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta'); const String upstreamRevision = ''; final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: upstreamRevision); fakeCommandRunner.remoteVersion = latestVersion; @@ -68,7 +68,7 @@ void main() { }); testUsingContext('throws tool exit with uncommitted changes', () async { - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev'); + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta'); const String upstreamRevision = ''; final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: upstreamRevision); fakeCommandRunner.remoteVersion = latestVersion; @@ -88,10 +88,10 @@ void main() { Platform: () => fakePlatform, }); - testUsingContext("Doesn't continue on known tag, dev branch, no force, already up-to-date", () async { + testUsingContext("Doesn't continue on known tag, beta branch, no force, already up-to-date", () async { const String revision = 'abc123'; final FakeFlutterVersion latestVersion = FakeFlutterVersion(frameworkRevision: revision); - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev', frameworkRevision: revision); + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta', frameworkRevision: revision); fakeCommandRunner.alreadyUpToDate = true; fakeCommandRunner.remoteVersion = latestVersion; @@ -118,7 +118,7 @@ void main() { const String upstreamVersion = '4.5.6'; final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', frameworkRevision: revision, frameworkRevisionShort: revision, frameworkVersion: version, @@ -246,7 +246,7 @@ void main() { testUsingContext('throws toolExit if repository url is null', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', repositoryUrl: null, ); @@ -265,7 +265,7 @@ void main() { testUsingContext('does not throw toolExit at standard remote url with FLUTTER_GIT_URL unset', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', ); expect(() => realCommandRunner.verifyStandardRemote(flutterVersion), returnsNormally); expect(processManager, hasNoRemainingExpectations); @@ -276,7 +276,7 @@ void main() { testUsingContext('throws toolExit at non-standard remote url with FLUTTER_GIT_URL unset', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', repositoryUrl: flutterNonStandardUrlDotGit, ); @@ -300,7 +300,7 @@ void main() { testUsingContext('does not throw toolExit at non-standard remote url with FLUTTER_GIT_URL set', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', repositoryUrl: flutterNonStandardUrlDotGit, ); @@ -315,7 +315,7 @@ void main() { testUsingContext('throws toolExit at remote url and FLUTTER_GIT_URL set to different urls', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', repositoryUrl: flutterNonStandardUrlDotGit, ); @@ -342,7 +342,7 @@ void main() { testUsingContext('exempts standard ssh url from check with FLUTTER_GIT_URL unset', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', repositoryUrl: flutterStandardSshUrl, ); @@ -413,7 +413,7 @@ void main() { const String upstreamVersion = '4.5.6'; final FakeFlutterVersion flutterVersion = FakeFlutterVersion( - channel: 'dev', + channel: 'beta', frameworkRevision: revision, frameworkVersion: version, ); @@ -474,7 +474,7 @@ void main() { testUsingContext('does not throw on unknown tag, official branch, force', () async { fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: null); - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev'); + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta'); final Future result = fakeCommandRunner.runCommand( force: true, @@ -492,7 +492,7 @@ void main() { }); testUsingContext('does not throw tool exit with uncommitted changes and force', () async { - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev'); + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta'); fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: null); fakeCommandRunner.willHaveUncommittedChanges = true; @@ -511,8 +511,8 @@ void main() { Platform: () => fakePlatform, }); - testUsingContext("Doesn't throw on known tag, dev branch, no force", () async { - final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'dev'); + testUsingContext("Doesn't throw on known tag, beta branch, no force", () async { + final FakeFlutterVersion flutterVersion = FakeFlutterVersion(channel: 'beta'); fakeCommandRunner.remoteVersion = FakeFlutterVersion(frameworkRevision: null); final Future result = fakeCommandRunner.runCommand( diff --git a/packages/flutter_tools/test/general.shard/channel_test.dart b/packages/flutter_tools/test/general.shard/channel_test.dart index 33d05521e4f..c0757910617 100644 --- a/packages/flutter_tools/test/general.shard/channel_test.dart +++ b/packages/flutter_tools/test/general.shard/channel_test.dart @@ -65,7 +65,6 @@ void main() { command: ['git', 'branch', '-r'], stdout: 'origin/beta\n' 'origin/master\n' - 'origin/dev\n' 'origin/stable\n', ), ); @@ -89,7 +88,6 @@ void main() { stdout: 'origin/beta\n' 'origin/master\n' 'origin/dependabot/bundler\n' - 'origin/dev\n' 'origin/v1.4.5-hotfixes\n' 'origin/stable\n', ), @@ -142,10 +140,8 @@ void main() { fakeProcessManager.addCommand( const FakeCommand( command: ['git', 'branch', '-r'], - stdout: 'origin/dev\n' - 'origin/beta\n' + stdout: 'origin/beta\n' 'origin/stable\n' - 'upstream/dev\n' 'upstream/beta\n' 'upstream/stable\n', ), @@ -165,7 +161,7 @@ void main() { .where((String line) => line?.isNotEmpty == true) .skip(1); // remove `Flutter channels:` line - expect(rows, ['dev', 'beta', 'stable', 'Currently not on an official channel.']); + expect(rows, ['beta', 'stable', 'Currently not on an official channel.']); }, overrides: { ProcessManager: () => fakeProcessManager, FileSystem: () => MemoryFileSystem.test(), diff --git a/packages/flutter_tools/test/general.shard/config_test.dart b/packages/flutter_tools/test/general.shard/config_test.dart index 1e0ac6a978f..006f0b083b3 100644 --- a/packages/flutter_tools/test/general.shard/config_test.dart +++ b/packages/flutter_tools/test/general.shard/config_test.dart @@ -63,6 +63,29 @@ void main() { expect(config.keys, isNot(contains('foo'))); }); + testWithoutContext('Config does not error on a file with a deprecated field', () { + final BufferLogger bufferLogger = BufferLogger.test(); + final File file = memoryFileSystem.file('.flutter_example') + ..writeAsStringSync(''' +{ + "is-bot": false, + "license-hash": "3e8c85e63b26ce223cda96a9a8fbb410", + "redisplay-welcome-message": true, + "last-devtools-activation-time": "2021-10-04 16:03:19.832823", + "last-active-stable-version": "b22742018b3edf16c6cadd7b76d9db5e7f9064b5" +} +'''); + config = Config( + 'example', + fileSystem: memoryFileSystem, + logger: bufferLogger, + platform: fakePlatform, + ); + + expect(file.existsSync(), isTrue); + expect(bufferLogger.errorText, isEmpty); + }); + testWithoutContext('Config parse error', () { final BufferLogger bufferLogger = BufferLogger.test(); final File file = memoryFileSystem.file('.flutter_example') diff --git a/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart b/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart index 7f55b6311aa..924b49e18cf 100644 --- a/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart +++ b/packages/flutter_tools/test/general.shard/persistent_tool_state_test.dart @@ -43,7 +43,6 @@ void main() { ); state1.updateLastActiveVersion('abc', Channel.master); - state1.updateLastActiveVersion('def', Channel.dev); state1.updateLastActiveVersion('ghi', Channel.beta); state1.updateLastActiveVersion('jkl', Channel.stable); @@ -53,7 +52,6 @@ void main() { ); expect(state2.lastActiveVersion(Channel.master), 'abc'); - expect(state2.lastActiveVersion(Channel.dev), 'def'); expect(state2.lastActiveVersion(Channel.beta), 'ghi'); expect(state2.lastActiveVersion(Channel.stable), 'jkl'); }); diff --git a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart index 0e0f91b39fa..07497676fdc 100644 --- a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart +++ b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart @@ -14,7 +14,7 @@ import '../src/common.dart'; import 'test_utils.dart'; const String _kInitialVersion = 'v1.9.1'; -const String _kBranch = 'dev'; +const String _kBranch = 'beta'; final Stdio stdio = Stdio(); final ProcessUtils processUtils = ProcessUtils(processManager: processManager, logger: StdoutLogger(