10
10
import 'dart:io' ;
11
11
12
12
import 'package:args/args.dart' ;
13
+ import 'package:meta/meta.dart' ;
13
14
14
15
const String kIncrement = 'increment' ;
15
16
const String kX = 'x' ;
@@ -20,131 +21,93 @@ const String kOrigin = 'origin';
20
21
const String kJustPrint = 'just-print' ;
21
22
const String kYes = 'yes' ;
22
23
const String kHelp = 'help' ;
24
+ const String kForce = 'force' ;
23
25
24
26
const String kUpstreamRemote = 'git@github.com:flutter/flutter.git' ;
25
27
26
28
void main (List <String > args) {
27
29
final ArgParser argParser = ArgParser (allowTrailingOptions: false );
28
30
29
- argParser.addOption (
30
- kIncrement,
31
- help: 'Specifies which part of the x.y.z version number to increment. Required.' ,
32
- valueHelp: 'level' ,
33
- allowed: < String > [kX, kY, kZ],
34
- allowedHelp: < String , String > {
35
- kX: 'Indicates a major development, e.g. typically changed after a big press event.' ,
36
- kY: 'Indicates a minor development, e.g. typically changed after a beta release.' ,
37
- kZ: 'Indicates the least notable level of change. You normally want this.' ,
38
- },
39
- );
40
- argParser.addOption (
41
- kCommit,
42
- help: 'Specifies which git commit to roll to the dev branch. Required.' ,
43
- valueHelp: 'hash' ,
44
- defaultsTo: null , // This option is required
45
- );
46
- argParser.addOption (
47
- kOrigin,
48
- help: 'Specifies the name of the upstream repository' ,
49
- valueHelp: 'repository' ,
50
- defaultsTo: 'upstream' ,
51
- );
52
- argParser.addFlag (
53
- kJustPrint,
54
- negatable: false ,
55
- help:
56
- "Don't actually roll the dev channel; "
57
- 'just print the would-be version and quit.' ,
58
- );
59
- argParser.addFlag (kYes, negatable: false , abbr: 'y' , help: 'Skip the confirmation prompt.' );
60
- argParser.addFlag (kHelp, negatable: false , help: 'Show this help message.' , hide: true );
61
-
62
31
ArgResults argResults;
63
32
try {
64
- argResults = argParser. parse ( args);
33
+ argResults = parseArguments (argParser, args);
65
34
} on ArgParserException catch (error) {
66
35
print (error.message);
67
36
print (argParser.usage);
68
37
exit (1 );
69
38
}
70
39
40
+ try {
41
+ run (
42
+ usage: argParser.usage,
43
+ argResults: argResults,
44
+ git: const Git (),
45
+ );
46
+ } on Exception catch (e) {
47
+ print (e.toString ());
48
+ exit (1 );
49
+ }
50
+ }
51
+
52
+ /// Main script execution.
53
+ ///
54
+ /// Returns true if publishing was successful, else false.
55
+ bool run ({
56
+ @required String usage,
57
+ @required ArgResults argResults,
58
+ @required Git git,
59
+ }) {
71
60
final String level = argResults[kIncrement] as String ;
72
61
final String commit = argResults[kCommit] as String ;
73
62
final String origin = argResults[kOrigin] as String ;
74
63
final bool justPrint = argResults[kJustPrint] as bool ;
75
64
final bool autoApprove = argResults[kYes] as bool ;
76
65
final bool help = argResults[kHelp] as bool ;
66
+ final bool force = argResults[kForce] as bool ;
77
67
78
68
if (help || level == null || commit == null ) {
79
- print ('roll_dev.dart --increment=level --commit=hash • update the version tags and roll a new dev build.\n ' );
80
- print (argParser.usage);
81
- exit (0 );
69
+ print (
70
+ 'roll_dev.dart --increment=level --commit=hash • update the version tags '
71
+ 'and roll a new dev build.\n $usage '
72
+ );
73
+ return false ;
82
74
}
83
75
84
- if (getGitOutput ('remote get-url $origin ' , 'check whether this is a flutter checkout' ) != kUpstreamRemote) {
85
- print ('The current directory is not a Flutter repository checkout with a correctly configured upstream remote.' );
86
- print ('For more details see: https://github.com/flutter/flutter/wiki/Release-process' );
87
- exit (1 );
76
+ final String remote = git.getOutput (
77
+ 'remote get-url $origin ' ,
78
+ 'check whether this is a flutter checkout' ,
79
+ );
80
+ if (remote != kUpstreamRemote) {
81
+ throw Exception (
82
+ 'The current directory is not a Flutter repository checkout with a '
83
+ 'correctly configured upstream remote.\n For more details see: '
84
+ 'https://github.com/flutter/flutter/wiki/Release-process'
85
+ );
88
86
}
89
87
90
- if (getGitOutput ('status --porcelain' , 'check status of your local checkout' ) != '' ) {
91
- print ('Your git repository is not clean. Try running "git clean -fd". Warning, this ' );
92
- print ('will delete files! Run with -n to find out which ones.' );
93
- exit (1 );
88
+ if (git.getOutput ('status --porcelain' , 'check status of your local checkout' ) != '' ) {
89
+ throw Exception (
90
+ 'Your git repository is not clean. Try running "git clean -fd". Warning, '
91
+ 'this will delete files! Run with -n to find out which ones.'
92
+ );
94
93
}
95
94
96
- runGit ('fetch $origin ' , 'fetch $origin ' );
97
- runGit ('reset $commit --hard' , 'reset to the release commit' );
95
+ // TODO(fujino): move this after `justPrint`
96
+ git.run ('fetch $origin ' , 'fetch $origin ' );
97
+ git.run ('reset $commit --hard' , 'reset to the release commit' );
98
98
99
- String version = getFullTag ();
100
- final Match match = parseFullTag (version);
101
- if (match == null ) {
102
- print ('Could not determine the version for this build.' );
103
- if (version.isNotEmpty)
104
- print ('Git reported the latest version as "$version ", which does not fit the expected pattern.' );
105
- exit (1 );
106
- }
99
+ String version = getFullTag (git);
107
100
108
- final List <int > parts = match.groups (< int > [1 , 2 , 3 , 4 , 5 ]).map <int >(int .parse).toList ();
109
-
110
- if (match.group (6 ) == '0' ) {
111
- print ('This commit has already been released, as version ${getVersionFromParts (parts )}.' );
112
- exit (0 );
113
- }
114
-
115
- switch (level) {
116
- case kX:
117
- parts[0 ] += 1 ;
118
- parts[1 ] = 0 ;
119
- parts[2 ] = 0 ;
120
- parts[3 ] = 0 ;
121
- parts[4 ] = 0 ;
122
- break ;
123
- case kY:
124
- parts[1 ] += 1 ;
125
- parts[2 ] = 0 ;
126
- parts[3 ] = 0 ;
127
- parts[4 ] = 0 ;
128
- break ;
129
- case kZ:
130
- parts[2 ] = 0 ;
131
- parts[3 ] += 1 ;
132
- parts[4 ] = 0 ;
133
- break ;
134
- default :
135
- print ('Unknown increment level. The valid values are "$kX ", "$kY ", and "$kZ ".' );
136
- exit (1 );
137
- }
138
- version = getVersionFromParts (parts);
101
+ version = incrementLevel (version, level);
139
102
140
103
if (justPrint) {
141
104
print (version);
142
- exit ( 0 ) ;
105
+ return false ;
143
106
}
144
107
145
- final String hash = getGitOutput ('rev-parse HEAD' , 'Get git hash for $commit ' );
108
+ final String hash = git. getOutput ('rev-parse HEAD' , 'Get git hash for $commit ' );
146
109
147
- runGit ('tag $version ' , 'tag the commit with the version label' );
110
+ git. run ('tag $version ' , 'tag the commit with the version label' );
148
111
149
112
// PROMPT
150
113
@@ -155,29 +118,79 @@ void main(List<String> args) {
155
118
'to the "dev" channel.' );
156
119
stdout.write ('Are you? [yes/no] ' );
157
120
if (stdin.readLineSync () != 'yes' ) {
158
- runGit ('tag -d $version ' , 'remove the tag you did not want to publish' );
121
+ git. run ('tag -d $version ' , 'remove the tag you did not want to publish' );
159
122
print ('The dev roll has been aborted.' );
160
- exit ( 0 ) ;
123
+ return false ;
161
124
}
162
125
}
163
126
164
- runGit ('push $origin $version ' , 'publish the version' );
165
- runGit ('push $origin HEAD:dev' , 'land the new version on the "dev" branch' );
127
+ git.run ('push $origin $version ' , 'publish the version' );
128
+ git.run (
129
+ 'push ${force ? "--force " : "" }$origin HEAD:dev' ,
130
+ 'land the new version on the "dev" branch' ,
131
+ );
166
132
print ('Flutter version $version has been rolled to the "dev" channel!' );
133
+ return true ;
134
+ }
135
+
136
+ ArgResults parseArguments (ArgParser argParser, List <String > args) {
137
+ argParser.addOption (
138
+ kIncrement,
139
+ help: 'Specifies which part of the x.y.z version number to increment. Required.' ,
140
+ valueHelp: 'level' ,
141
+ allowed: < String > [kX, kY, kZ],
142
+ allowedHelp: < String , String > {
143
+ kX: 'Indicates a major development, e.g. typically changed after a big press event.' ,
144
+ kY: 'Indicates a minor development, e.g. typically changed after a beta release.' ,
145
+ kZ: 'Indicates the least notable level of change. You normally want this.' ,
146
+ },
147
+ );
148
+ argParser.addOption (
149
+ kCommit,
150
+ help: 'Specifies which git commit to roll to the dev branch. Required.' ,
151
+ valueHelp: 'hash' ,
152
+ defaultsTo: null , // This option is required
153
+ );
154
+ argParser.addOption (
155
+ kOrigin,
156
+ help: 'Specifies the name of the upstream repository' ,
157
+ valueHelp: 'repository' ,
158
+ defaultsTo: 'upstream' ,
159
+ );
160
+ argParser.addFlag (
161
+ kForce,
162
+ abbr: 'f' ,
163
+ help: 'Force push. Necessary when the previous release had cherry-picks.' ,
164
+ negatable: false ,
165
+ );
166
+ argParser.addFlag (
167
+ kJustPrint,
168
+ negatable: false ,
169
+ help:
170
+ "Don't actually roll the dev channel; "
171
+ 'just print the would-be version and quit.' ,
172
+ );
173
+ argParser.addFlag (kYes, negatable: false , abbr: 'y' , help: 'Skip the confirmation prompt.' );
174
+ argParser.addFlag (kHelp, negatable: false , help: 'Show this help message.' , hide: true );
175
+
176
+ return argParser.parse (args);
167
177
}
168
178
169
- String getFullTag () {
179
+ /// Obtain the version tag of the previous dev release.
180
+ String getFullTag (Git git) {
170
181
const String glob = '*.*.*-*.*.pre' ;
171
- return getGitOutput (
172
- 'describe --match $glob --first-parent --long --tags' ,
182
+ // describe the latest dev release
183
+ const String ref = 'refs/heads/dev' ;
184
+ return git.getOutput (
185
+ 'describe --match $glob --exact-match --tags $ref ' ,
173
186
'obtain last released version number' ,
174
187
);
175
188
}
176
189
177
190
Match parseFullTag (String version) {
178
- // of the form: x.y.z-m.n.pre-c-g<revision>
191
+ // of the form: x.y.z-m.n.pre
179
192
final RegExp versionPattern = RegExp (
180
- r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre-(\d+)-g([a-f0-9]+) $' );
193
+ r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre$' );
181
194
return versionPattern.matchAsPrefix (version);
182
195
}
183
196
@@ -195,33 +208,78 @@ String getVersionFromParts(List<int> parts) {
195
208
return buf.toString ();
196
209
}
197
210
198
- String getGitOutput (String command, String explanation) {
199
- final ProcessResult result = _runGit (command);
200
- if ((result.stderr as String ).isEmpty && result.exitCode == 0 )
201
- return (result.stdout as String ).trim ();
202
- _reportGitFailureAndExit (result, explanation);
203
- return null ; // for the analyzer's sake
204
- }
211
+ class Git {
212
+ const Git ();
205
213
206
- void runGit (String command, String explanation) {
207
- final ProcessResult result = _runGit (command);
208
- if (result.exitCode != 0 )
209
- _reportGitFailureAndExit (result, explanation);
210
- }
214
+ String getOutput (String command, String explanation) {
215
+ final ProcessResult result = _run (command);
216
+ if ((result.stderr as String ).isEmpty && result.exitCode == 0 )
217
+ return (result.stdout as String ).trim ();
218
+ _reportFailureAndExit (result, explanation);
219
+ return null ; // for the analyzer's sake
220
+ }
221
+
222
+ void run (String command, String explanation) {
223
+ final ProcessResult result = _run (command);
224
+ if (result.exitCode != 0 )
225
+ _reportFailureAndExit (result, explanation);
226
+ }
227
+
228
+ ProcessResult _run (String command) {
229
+ return Process .runSync ('git' , command.split (' ' ));
230
+ }
211
231
212
- ProcessResult _runGit (String command) {
213
- return Process .runSync ('git' , command.split (' ' ));
232
+ void _reportFailureAndExit (ProcessResult result, String explanation) {
233
+ if (result.exitCode != 0 ) {
234
+ print ('Failed to $explanation . Git exited with error code ${result .exitCode }.' );
235
+ } else {
236
+ print ('Failed to $explanation .' );
237
+ }
238
+ if ((result.stdout as String ).isNotEmpty)
239
+ print ('stdout from git:\n ${result .stdout }\n ' );
240
+ if ((result.stderr as String ).isNotEmpty)
241
+ print ('stderr from git:\n ${result .stderr }\n ' );
242
+ exit (1 );
243
+ }
214
244
}
215
245
216
- void _reportGitFailureAndExit (ProcessResult result, String explanation) {
217
- if (result.exitCode != 0 ) {
218
- print ('Failed to $explanation . Git exited with error code ${result .exitCode }.' );
219
- } else {
220
- print ('Failed to $explanation .' );
246
+ /// Return a copy of the [version] with [level] incremented by one.
247
+ String incrementLevel (String version, String level) {
248
+ final Match match = parseFullTag (version);
249
+ if (match == null ) {
250
+ String errorMessage;
251
+ if (version.isEmpty) {
252
+ errorMessage = 'Could not determine the version for this build.' ;
253
+ } else {
254
+ errorMessage = 'Git reported the latest version as "$version ", which '
255
+ 'does not fit the expected pattern.' ;
256
+ }
257
+ throw Exception (errorMessage);
258
+ }
259
+
260
+ final List <int > parts = match.groups (< int > [1 , 2 , 3 , 4 , 5 ]).map <int >(int .parse).toList ();
261
+
262
+ switch (level) {
263
+ case kX:
264
+ parts[0 ] += 1 ;
265
+ parts[1 ] = 0 ;
266
+ parts[2 ] = 0 ;
267
+ parts[3 ] = 0 ;
268
+ parts[4 ] = 0 ;
269
+ break ;
270
+ case kY:
271
+ parts[1 ] += 1 ;
272
+ parts[2 ] = 0 ;
273
+ parts[3 ] = 0 ;
274
+ parts[4 ] = 0 ;
275
+ break ;
276
+ case kZ:
277
+ parts[2 ] = 0 ;
278
+ parts[3 ] += 1 ;
279
+ parts[4 ] = 0 ;
280
+ break ;
281
+ default :
282
+ throw Exception ('Unknown increment level. The valid values are "$kX ", "$kY ", and "$kZ ".' );
221
283
}
222
- if ((result.stdout as String ).isNotEmpty)
223
- print ('stdout from git:\n ${result .stdout }\n ' );
224
- if ((result.stderr as String ).isNotEmpty)
225
- print ('stderr from git:\n ${result .stderr }\n ' );
226
- exit (1 );
284
+ return getVersionFromParts (parts);
227
285
}
0 commit comments