@@ -18,6 +18,15 @@ import '../version.dart';
18
18
import 'channel.dart' ;
19
19
20
20
class UpgradeCommand extends FlutterCommand {
21
+ UpgradeCommand () {
22
+ argParser.addFlag (
23
+ 'force' ,
24
+ abbr: 'f' ,
25
+ help: 'force upgrade the flutter branch, potentially discarding local changes.' ,
26
+ negatable: false ,
27
+ );
28
+ }
29
+
21
30
@override
22
31
final String name = 'upgrade' ;
23
32
@@ -29,64 +38,182 @@ class UpgradeCommand extends FlutterCommand {
29
38
30
39
@override
31
40
Future <FlutterCommandResult > runCommand () async {
41
+ final UpgradeCommandRunner upgradeCommandRunner = UpgradeCommandRunner ();
42
+ await upgradeCommandRunner.runCommand (argResults['force' ], GitTagVersion .determine (), FlutterVersion .instance);
43
+ return null ;
44
+ }
45
+ }
46
+
47
+
48
+ @visibleForTesting
49
+ class UpgradeCommandRunner {
50
+ Future <FlutterCommandResult > runCommand (bool force, GitTagVersion gitTagVersion, FlutterVersion flutterVersion) async {
51
+ await verifyUpstreamConfigured ();
52
+ if (! force && gitTagVersion == const GitTagVersion .unknown ()) {
53
+ // If the commit is a recognized branch and not master,
54
+ // explain that we are avoiding potential damage.
55
+ if (flutterVersion.channel != 'master' && FlutterVersion .officialChannels.contains (flutterVersion.channel)) {
56
+ throwToolExit (
57
+ 'Unknown flutter tag. Abandoning upgrade to avoid destroying local '
58
+ 'changes. It is recommended to use git directly if not working off of '
59
+ 'an official channel.'
60
+ );
61
+ // Otherwise explain that local changes can be lost.
62
+ } else {
63
+ throwToolExit (
64
+ 'Unknown flutter tag. Abandoning upgrade to avoid destroying local '
65
+ 'changes. If it is okay to remove local changes, then re-run this '
66
+ 'command with --force.'
67
+ );
68
+ }
69
+ }
70
+ final String stashName = await maybeStash (gitTagVersion);
71
+ await upgradeChannel (flutterVersion);
72
+ await attemptRebase ();
73
+ await precacheArtifacts ();
74
+ await updatePackages (flutterVersion);
75
+ await runDoctor ();
76
+ await applyStash (stashName);
77
+ return null ;
78
+ }
79
+
80
+ /// Check if there is an upstream repository configured.
81
+ ///
82
+ /// Exits tool if there is no upstream.
83
+ Future <void > verifyUpstreamConfigured () async {
32
84
try {
33
85
await runCheckedAsync (< String > [
34
86
'git' , 'rev-parse' , '@{u}' ,
35
87
], workingDirectory: Cache .flutterRoot);
36
88
} catch (e) {
37
- throwToolExit ('Unable to upgrade Flutter: no upstream repository configured.' );
89
+ throwToolExit (
90
+ 'Unable to upgrade Flutter: no upstream repository configured. '
91
+ 'Run \' git remote add upstream '
92
+ 'https://github.com/flutter/flutter\' in ${Cache .flutterRoot }' ,
93
+ );
38
94
}
95
+ }
39
96
40
- final FlutterVersion flutterVersion = FlutterVersion .instance;
97
+ /// Attempt to stash any local changes.
98
+ ///
99
+ /// Returns the stash name if any changes were stashed. Exits tool if
100
+ /// `git stash` returns a non-zero exit code.
101
+ Future <String > maybeStash (GitTagVersion gitTagVersion) async {
102
+ final String stashName = 'flutter-upgrade-from-v${gitTagVersion .x }.${gitTagVersion .y }.${gitTagVersion .z }' ;
103
+ try {
104
+ final RunResult runResult = await runCheckedAsync (< String > [
105
+ 'git' , 'stash' , 'push' , '-m' , stashName
106
+ ]);
107
+ // output message will contain stash name if any changes were stashed..
108
+ if (runResult.stdout.contains (stashName)) {
109
+ return stashName;
110
+ }
111
+ } catch (e) {
112
+ throwToolExit ('Failed to stash local changes: $e ' );
113
+ }
114
+ return null ;
115
+ }
41
116
117
+ /// Attempts to upgrade the channel.
118
+ ///
119
+ /// If the user is on a deprecated channel, attempts to migrate them off of
120
+ /// it.
121
+ Future <void > upgradeChannel (FlutterVersion flutterVersion) async {
42
122
printStatus ('Upgrading Flutter from ${Cache .flutterRoot }...' );
43
-
44
123
await ChannelCommand .upgradeChannel ();
124
+ }
45
125
46
- int code = await runCommandAndStreamOutput (
47
- < String > ['git' , 'pull' , '--ff-only' ],
126
+ /// Attempts to rebase the upstream onto the local branch.
127
+ ///
128
+ /// If there haven't been any hot fixes or local changes, this is equivalent
129
+ /// to a fast-forward.
130
+ Future <void > attemptRebase () async {
131
+ final int code = await runCommandAndStreamOutput (
132
+ < String > ['git' , 'pull' , '--rebase' ],
48
133
workingDirectory: Cache .flutterRoot,
49
134
mapFunction: (String line) => matchesGitLine (line) ? null : line,
50
135
);
51
-
52
- if (code != 0 )
136
+ if (code != 0 ) {
137
+ printError ('git rebase failed' );
138
+ final int undoCode = await runCommandAndStreamOutput (
139
+ < String > ['git' , 'rebase' , '--abort' ],
140
+ workingDirectory: Cache .flutterRoot,
141
+ mapFunction: (String line) => matchesGitLine (line) ? null : line,
142
+ );
143
+ if (undoCode != 0 ) {
144
+ printError (
145
+ 'Failed to apply rebase: The flutter installation at'
146
+ ' ${Cache .flutterRoot } may be corrupted. A reinstallation of Flutter '
147
+ 'is recommended'
148
+ );
149
+ }
53
150
throwToolExit (null , exitCode: code);
151
+ }
152
+ }
54
153
55
- // Check for and download any engine and pkg/ updates.
56
- // We run the 'flutter' shell script re-entrantly here
57
- // so that it will download the updated Dart and so forth
58
- // if necessary.
154
+ /// Update the engine repository and precache all artifacts.
155
+ ///
156
+ /// Check for and download any engine and pkg/ updates. We run the 'flutter'
157
+ /// shell script re-entrantly here so that it will download the updated
158
+ /// Dart and so forth if necessary.
159
+ Future <void > precacheArtifacts () async {
59
160
printStatus ('' );
60
161
printStatus ('Upgrading engine...' );
61
- code = await runCommandAndStreamOutput (
162
+ final int code = await runCommandAndStreamOutput (
62
163
< String > [
63
164
fs.path.join ('bin' , 'flutter' ), '--no-color' , '--no-version-check' , 'precache' ,
64
165
],
65
166
workingDirectory: Cache .flutterRoot,
66
167
allowReentrantFlutter: true ,
67
168
);
169
+ if (code != 0 ) {
170
+ throwToolExit (null , exitCode: code);
171
+ }
172
+ }
68
173
174
+ /// Update the user's packages.
175
+ Future <void > updatePackages (FlutterVersion flutterVersion) async {
69
176
printStatus ('' );
70
177
printStatus (flutterVersion.toString ());
71
-
72
178
final String projectRoot = findProjectRoot ();
73
179
if (projectRoot != null ) {
74
180
printStatus ('' );
75
181
await pubGet (context: PubContext .pubUpgrade, directory: projectRoot, upgrade: true , checkLastModified: false );
76
182
}
183
+ }
77
184
78
- // Run a doctor check in case system requirements have changed.
185
+ /// Run flutter doctor in case requirements have changed.
186
+ Future <void > runDoctor () async {
79
187
printStatus ('' );
80
188
printStatus ('Running flutter doctor...' );
81
- code = await runCommandAndStreamOutput (
189
+ await runCommandAndStreamOutput (
82
190
< String > [
83
191
fs.path.join ('bin' , 'flutter' ), '--no-version-check' , 'doctor' ,
84
192
],
85
193
workingDirectory: Cache .flutterRoot,
86
194
allowReentrantFlutter: true ,
87
195
);
196
+ }
88
197
89
- return null ;
198
+ /// Pop stash changes if [stashName] is non-null and contained in stash.
199
+ Future <void > applyStash (String stashName) async {
200
+ if (stashName == null ) {
201
+ return ;
202
+ }
203
+ try {
204
+ final RunResult result = await runCheckedAsync (< String > [
205
+ 'git' , 'stash' , 'list'
206
+ ]);
207
+ if (! result.stdout.contains (stashName)) {
208
+ // print the same warning as if this threw.
209
+ throw Exception ();
210
+ }
211
+ await runCheckedAsync (< String > [
212
+ 'git' , 'stash' , 'pop' ,
213
+ ]);
214
+ } catch (e) {
215
+ printError ('Failed to re-apply local changes. State may have been lost.' );
216
+ }
90
217
}
91
218
92
219
// dev/benchmarks/complex_layout/lib/main.dart | 24 +-
@@ -97,7 +224,6 @@ class UpgradeCommand extends FlutterCommand {
97
224
// create mode 100644 examples/flutter_gallery/lib/gallery/demo.dart
98
225
static final RegExp _gitChangedRegex = RegExp (r' (rename|delete mode|create mode) .+' );
99
226
100
- @visibleForTesting
101
227
static bool matchesGitLine (String line) {
102
228
return _gitDiffRegex.hasMatch (line)
103
229
|| _gitChangedRegex.hasMatch (line)
0 commit comments