1// Copyright 2014 The Flutter 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 'package:process/process.dart';
6
7import '../base/common.dart';
8import '../base/file_system.dart';
9import '../base/io.dart';
10import '../base/logger.dart';
11import '../base/process.dart';
12import '../base/terminal.dart';
13import '../cache.dart';
14import '../globals.dart' as globals;
15import '../persistent_tool_state.dart';
16import '../runner/flutter_command.dart';
17import '../version.dart';
18
19/// The flutter downgrade command returns the SDK to the last recorded version
20/// for a particular branch.
21///
22/// For example, suppose a user on the beta channel upgrades from 1.2.3 to 1.4.6.
23/// The tool will record that sha "abcdefg" was the last active beta channel in the
24/// persistent tool state. If the user is still on the beta channel and runs
25/// flutter downgrade, this will take the user back to "abcdefg". They will not be
26/// able to downgrade again, since the tool only records one prior version.
27/// Additionally, if they had switched channels to stable before trying to downgrade,
28/// the command would fail since there was no previously recorded stable version.
29class DowngradeCommand extends FlutterCommand {
30 DowngradeCommand({
31 bool verboseHelp = false,
32 PersistentToolState? persistentToolState,
33 required Logger logger,
34 ProcessManager? processManager,
35 FlutterVersion? flutterVersion,
36 Terminal? terminal,
37 Stdio? stdio,
38 FileSystem? fileSystem,
39 }) : _terminal = terminal,
40 _flutterVersion = flutterVersion,
41 _persistentToolState = persistentToolState,
42 _processManager = processManager,
43 _stdio = stdio,
44 _logger = logger,
45 _fileSystem = fileSystem {
46 argParser.addOption(
47 'working-directory',
48 hide: !verboseHelp,
49 help: 'Override the downgrade working directory. '
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.'
53 );
54 argParser.addFlag(
55 'prompt',
56 defaultsTo: true,
57 hide: !verboseHelp,
58 help: 'Show the downgrade prompt.'
59 );
60 }
61
62 Terminal? _terminal;
63 FlutterVersion? _flutterVersion;
64 PersistentToolState? _persistentToolState;
65 ProcessUtils? _processUtils;
66 ProcessManager? _processManager;
67 final Logger _logger;
68 Stdio? _stdio;
69 FileSystem? _fileSystem;
70
71 @override
72 String get description => 'Downgrade Flutter to the last active version for the current channel.';
73
74 @override
75 String get name => 'downgrade';
76
77 @override
78 final String category = FlutterCommandCategory.sdk;
79
80 @override
81 Future<FlutterCommandResult> runCommand() async {
82 // Commands do not necessarily have access to the correct zone injected
83 // values when being created. Fields must be lazily instantiated in runCommand,
84 // at least until the zone injection is refactored.
85 _terminal ??= globals.terminal;
86 _flutterVersion ??= globals.flutterVersion;
87 _persistentToolState ??= globals.persistentToolState;
88 _processManager ??= globals.processManager;
89 _processUtils ??= ProcessUtils(processManager: _processManager!, logger: _logger);
90 _stdio ??= globals.stdio;
91 _fileSystem ??= globals.fs;
92 String workingDirectory = Cache.flutterRoot!;
93 if (argResults!.wasParsed('working-directory')) {
94 workingDirectory = stringArg('working-directory')!;
95 _flutterVersion = FlutterVersion(
96 fs: _fileSystem!,
97 flutterRoot: workingDirectory,
98 );
99 }
100
101 final String currentChannel = _flutterVersion!.channel;
102 final Channel? channel = getChannelForName(currentChannel);
103 if (channel == null) {
104 throwToolExit(
105 'Flutter is not currently on a known channel. '
106 'Use "flutter channel" to switch to an official channel. '
107 );
108 }
109 final PersistentToolState persistentToolState = _persistentToolState!;
110 final String? lastFlutterVersion = persistentToolState.lastActiveVersion(channel);
111 final String? currentFlutterVersion = _flutterVersion?.frameworkRevision;
112 if (lastFlutterVersion == null || currentFlutterVersion == lastFlutterVersion) {
113 final String trailing = await _createErrorMessage(
114 workingDirectory,
115 channel,
116 );
117 throwToolExit(
118 "It looks like you haven't run "
119 '"flutter upgrade" on channel "$currentChannel".\n'
120 '\n'
121 '"flutter downgrade" undoes the last "flutter upgrade".\n'
122 '\n'
123 'To switch to a specific Flutter version, see: '
124 'https://flutter.dev/to/switch-flutter-version'
125 '$trailing'
126 );
127 }
128
129 // Detect unknown versions.
130 final ProcessUtils processUtils = _processUtils!;
131 final RunResult parseResult = await processUtils.run(<String>[
132 'git', 'describe', '--tags', lastFlutterVersion,
133 ], workingDirectory: workingDirectory);
134 if (parseResult.exitCode != 0) {
135 throwToolExit('Failed to parse version for downgrade:\n${parseResult.stderr}');
136 }
137 final String humanReadableVersion = parseResult.stdout;
138
139 // If there is a terminal attached, prompt the user to confirm the downgrade.
140 final Stdio stdio = _stdio!;
141 final Terminal terminal = _terminal!;
142 if (stdio.hasTerminal && boolArg('prompt')) {
143 terminal.usesTerminalUi = true;
144 final String result = await terminal.promptForCharInput(
145 const <String>['y', 'n'],
146 prompt: 'Downgrade flutter to version $humanReadableVersion?',
147 logger: _logger,
148 );
149 if (result == 'n') {
150 return FlutterCommandResult.success();
151 }
152 } else {
153 _logger.printStatus('Downgrading Flutter to version $humanReadableVersion');
154 }
155
156 // To downgrade the tool, we perform a git checkout --hard, and then
157 // switch channels. The version recorded must have existed on that branch
158 // so this operation is safe.
159 try {
160 await processUtils.run(
161 <String>['git', 'reset', '--hard', lastFlutterVersion],
162 throwOnError: true,
163 workingDirectory: workingDirectory,
164 );
165 } on ProcessException catch (error) {
166 throwToolExit(
167 'Unable to downgrade Flutter: The tool could not update to the version '
168 '$humanReadableVersion.\n'
169 'Error: $error'
170 );
171 }
172 try {
173 await processUtils.run(
174 // The `--` bit (because it's followed by nothing) means that we don't actually change
175 // anything in the working tree, which avoids the need to first go into detached HEAD mode.
176 <String>['git', 'checkout', currentChannel, '--'],
177 throwOnError: true,
178 workingDirectory: workingDirectory,
179 );
180 } on ProcessException catch (error) {
181 throwToolExit(
182 'Unable to downgrade Flutter: The tool could not switch to the channel '
183 '$currentChannel.\n'
184 'Error: $error'
185 );
186 }
187 await FlutterVersion.resetFlutterVersionFreshnessCheck();
188 _logger.printStatus('Success');
189 return FlutterCommandResult.success();
190 }
191
192 // Formats an error message that lists the currently stored versions.
193 Future<String> _createErrorMessage(
194 String workingDirectory,
195 Channel currentChannel,
196 ) async {
197 final StringBuffer buffer = StringBuffer();
198 for (final Channel channel in Channel.values) {
199 if (channel == currentChannel) {
200 continue;
201 }
202 final String? sha = _persistentToolState?.lastActiveVersion(channel);
203 if (sha == null) {
204 continue;
205 }
206 final RunResult parseResult = await _processUtils!.run(
207 <String>['git', 'describe', '--tags', sha],
208 workingDirectory: workingDirectory,
209 );
210 if (parseResult.exitCode == 0) {
211 if (buffer.isEmpty) {
212 buffer.writeln();
213 }
214 buffer.writeln();
215 buffer.writeln(
216 'Channel "${getNameForChannel(channel)}" was previously on: '
217 '${parseResult.stdout}.'
218 );
219 }
220 }
221 return buffer.toString();
222 }
223}
224

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com